#include "src/ir/translator.h"

#include <cstdio>
#include <cstdlib>
#include <optional>
#include <unordered_map>

#include "hermes/AST/SemValidate.h"
#include "hermes/BCGen/PBC/PBC.h"
#include "hermes/FrontEndDefs/JavaScriptDeclKind.h"
#include "hermes/IR/IR.h"
#include "hermes/IR/IRBuilder.h"
#include "hermes/IR/IRVerifier.h"
#include "hermes/IR/Instrs.h"
#include "hermes/Optimizer/PassManager/Pipeline.h"
#include "hermes/Support/InternalIdentifierMaker.h"
#include "hermes/Support/UTF8.h"
#include "llvh/ADT/ScopeExit.h"
#include "llvh/IR/Metadata.h"
#include "llvh/Support/SaveAndRestore.h"
#include "src/ast/ast-source-ranges.h"
#include "src/ast/ast.h"
#include "src/ast/prettyprinter.h"
#include "src/ast/scopes.h"
#include "src/base/logging.h"
#include "src/codegen/unoptimized-compilation-info.h"
#include "src/execution/isolate.h"
#include "src/execution/vm-state-inl.h"
#include "src/interpreter/bytecode-flags-and-tokens.h"
#include "src/numbers/hash-seed-inl.h"
#include "src/parsing/parse-info.h"
#include "src/parsing/parsing.h"
#include "src/pbc/portable-function-syntax-kind.h"
#include "src/pbc/portable-literals.h"
#include "src/zone/zone-type-traits.h"

namespace {
// TODO:(wwq) Enable block scoping by default when feature ready.
bool enableBlockScoping = false;
}  // namespace

using namespace v8::internal;
#define LOG_ERROR std::cerr << "AOT "
namespace v8 {
namespace aot {
struct LiteralHasher {
  using LiteralType = Literal*;
  size_t operator()(const LiteralType& v) const { return v->Hash(); }
};
struct LiteralEqual {
  using LiteralType = Literal*;
  bool operator()(const LiteralType& lhs, const LiteralType& rhs) const {
    return Literal::Match(lhs, rhs);
  }
};

inline hermes::UnaryOperatorInst::OpKind getUnaryOperatorInstKind(
    Token::Value kind);
inline hermes::BinaryOperatorInst::OpKind getBinaryOperatorInstKind(
    Token::Value kind);
enum class ControlFlowChange { Break, Continue };

inline bool isLogicalKind(Token::Value kind) {
  switch (kind) {
    case Token::kAnd:
    case Token::kOr:
    case Token::kNullish:
      return true;
    default:
      return false;
  }
}

class EncodingUtils {
 public:
  enum EncodingType {
    kEncodingAscii,
    kEncodingLatin1,
    kEncodingUTF8,
    kEncodingUTF16,
  };

  // @return converted. If `converted` is false, the `out` is invalid
  static bool convertToUTF8(const AstRawString* str, std::string& out) {
    EncodingType from_type = kEncodingAscii;
    const uint8_t* data = str->raw_data();
    size_t byte_length = str->byte_length();
    if (str->is_one_byte()) {
      bool ascii = hermes::isAllASCII(data, data + byte_length);
      from_type = ascii ? kEncodingAscii : kEncodingLatin1;
    } else {
      from_type = kEncodingUTF16;
    }
    return convertToUTF8(data, byte_length, from_type, out);
  }

  static void convertToUTF8(const AstConsString* cons_string,
                            v8::internal::LocalIsolate* isolate,
                            std::string& out) {
    Handle<v8::internal::String> str = cons_string->AllocateFlat(isolate);
    CHECK(str->IsFlat());
    // ToCString returns a UTF8 representation of the string.
    uint32_t utf8_length = 0;
    std::unique_ptr<char[]> cstr = str->ToCString(&utf8_length);
    out = std::string(cstr.get(), utf8_length);
  }

  // @return converted. If `converted` is false, the `out` is invalid
  static bool convertToUTF8(const uint8_t* str, size_t byte_length,
                            EncodingType from_type, std::string& out) {
    switch (from_type) {
      case kEncodingAscii:
      case kEncodingUTF8: {
        return false;
      }
      case kEncodingLatin1: {
        convertLatin1ToUTF8(str, byte_length, out);
        return true;
      }
      case kEncodingUTF16: {
        convertUTF16ToUTF8(str, byte_length, out);
        return true;
      }
    }
    FATAL(__FUNCTION__);
  }

 private:
  static void convertUTF16ToUTF8(const uint8_t* str, size_t byte_length,
                                 std::string& out) {
    CHECK(byte_length % sizeof(char16_t) == 0);
    llvh::ArrayRef<char16_t> input(reinterpret_cast<const char16_t*>(str),
                                   byte_length / sizeof(char16_t));
    hermes::convertUTF16ToUTF8WithSingleSurrogates(out, input);
  }

  static void convertLatin1ToUTF8(const uint8_t* str, size_t byte_length,
                                  std::string& out) {
    std::vector<char16_t> utf16_buffer;
    for (size_t i = 0; i < byte_length; ++i) {
      utf16_buffer.push_back(static_cast<char16_t>(str[i]));
    }
    convertUTF16ToUTF8(reinterpret_cast<uint8_t*>(utf16_buffer.data()),
                       utf16_buffer.size() * sizeof(char16_t), out);
  }
};

using EagerInnerLiteralsType =
    std::vector<std::pair<FunctionLiteral*, hermes::Function*>>;
using VarDecl = hermes::sem::FunctionInfo::VarDecl;

VarDecl::Kind getVarDeclKind(Variable* variable) {
  if (!variable->maybe_assigned()) {
    return VarDecl::Kind::Const;
  }
  switch (variable->mode()) {
    case VariableMode::kConst:
      return VarDecl::Kind::Const;
    case VariableMode::kVar:
      return VarDecl::Kind::Var;
    case VariableMode::kLet:
      return VarDecl::Kind::Let;
    default:
      return VarDecl::Kind::Var;
  }
}

hermes::LiteralClass::Property::Kind getClassLiteralPropertyKind(
    ClassLiteral::Property* prop) {
  switch (prop->kind()) {
    case ClassLiteral::Property::METHOD:
      return hermes::LiteralClass::Property::Kind::METHOD;
    case ClassLiteral::Property::GETTER:
      return hermes::LiteralClass::Property::Kind::GETTER;
    case ClassLiteral::Property::SETTER:
      return hermes::LiteralClass::Property::Kind::SETTER;
    case ClassLiteral::Property::FIELD:
      return hermes::LiteralClass::Property::Kind::FIELD;
    case ClassLiteral::Property::AUTO_ACCESSOR:
      return hermes::LiteralClass::Property::Kind::AUTO_ACCESSOR;
  }
}

class NameTable {
 public:
  void insertIntoScope(Variable* var, hermes::Variable* value) {
    nameTable_[var->scope()][var] = value;
  }

  hermes::Variable* lookup(Variable* var) {
    Scope* scope = var->scope();
    CHECK(nameTable_.count(scope));
    CHECK(nameTable_[scope].count(var));
    return nameTable_[scope][var];
  }

  hermes::Variable* find(Variable* var) {
    auto itr = nameTable_.find(var->scope());
    if (itr == nameTable_.end()) {
      return nullptr;
    }
    auto value = itr->second.find(var);
    if (value == itr->second.end()) {
      return nullptr;
    }
    return value->second;
  }

 private:
  // TODO: Maybe use std::pair<Scope*, size_t> as key type.
  std::unordered_map<Scope*, std::unordered_map<Variable*, hermes::Variable*>>
      nameTable_;
};

class AssignmentLhsData {
 public:
  AssignmentLhsData() = default;
  AssignmentLhsData(const AssignmentLhsData& lhs) = default;
  AssignmentLhsData& operator=(const AssignmentLhsData& lhs) = default;

  static AssignmentLhsData NonProperty(Expression* expr);
  static AssignmentLhsData NamedProperty(Expression* object_expr,
                                         hermes::Value* object,
                                         const AstRawString* name);
  static AssignmentLhsData KeyedProperty(hermes::Value* object,
                                         hermes::Value* key);
  static AssignmentLhsData PrivateMethodOrAccessor(AssignType type,
                                                   Property* property,
                                                   hermes::Value* object,
                                                   hermes::Value* key);
  static AssignmentLhsData PrivateDebugEvaluate(AssignType type,
                                                Property* property,
                                                hermes::Value* object);
  static AssignmentLhsData NamedSuperProperty(
      const hermes::CallInst::ArgumentList& super_property_args);
  static AssignmentLhsData KeyedSuperProperty(
      const hermes::CallInst::ArgumentList& super_property_args);

  AssignType assign_type() const { return assign_type_; }
  bool is_private_assign_type() const {
    return assign_type_ == PRIVATE_METHOD ||
           assign_type_ == PRIVATE_GETTER_ONLY ||
           assign_type_ == PRIVATE_SETTER_ONLY ||
           assign_type_ == PRIVATE_GETTER_AND_SETTER ||
           assign_type_ == PRIVATE_DEBUG_DYNAMIC;
  }
  Expression* expr() const {
    CHECK(assign_type_ == NON_PROPERTY || is_private_assign_type());
    return expr_;
  }
  Expression* object_expr() const {
    CHECK_EQ(assign_type_, NAMED_PROPERTY);
    return object_expr_;
  }
  hermes::Value* object() const {
    CHECK(assign_type_ == NAMED_PROPERTY || assign_type_ == KEYED_PROPERTY ||
          is_private_assign_type());
    return object_;
  }
  hermes::Value* key() const {
    CHECK((assign_type_ == KEYED_PROPERTY || is_private_assign_type()) &&
          assign_type_ != PRIVATE_DEBUG_DYNAMIC);
    return key_;
  }
  const AstRawString* name() const {
    CHECK(assign_type_ == NAMED_PROPERTY);
    return name_;
  }
  hermes::CallInst::ArgumentList super_property_args() const {
    CHECK(assign_type_ == NAMED_SUPER_PROPERTY ||
          assign_type_ == KEYED_SUPER_PROPERTY);
    return super_property_args_;
  }

 private:
  AssignmentLhsData(AssignType assign_type, Expression* expr,
                    const hermes::CallInst::ArgumentList& super_property_args,
                    hermes::Value* object, hermes::Value* key,
                    Expression* object_expr, const AstRawString* name)
      : assign_type_(assign_type),
        expr_(expr),
        super_property_args_(super_property_args),
        object_(object),
        key_(key),
        object_expr_(object_expr),
        name_(name) {}

  AssignType assign_type_;

  // Different assignment types use different fields:
  //
  // NON_PROPERTY: expr
  // NAMED_PROPERTY: object_expr, object, name
  // KEYED_PROPERTY, PRIVATE_METHOD: object, key
  // NAMED_SUPER_PROPERTY: super_property_args
  // KEYED_SUPER_PROPERT:  super_property_args
  Expression* expr_;
  hermes::CallInst::ArgumentList super_property_args_;
  hermes::Value* object_;
  hermes::Value* key_;
  Expression* object_expr_;
  const AstRawString* name_;
};

// static
AssignmentLhsData AssignmentLhsData::NonProperty(Expression* expr) {
  return AssignmentLhsData(NON_PROPERTY, expr, {}, nullptr, nullptr, nullptr,
                           nullptr);
}
// static
AssignmentLhsData AssignmentLhsData::NamedProperty(Expression* object_expr,
                                                   hermes::Value* object,
                                                   const AstRawString* name) {
  return AssignmentLhsData(NAMED_PROPERTY, nullptr, {}, object, nullptr,
                           object_expr, name);
}
// static
AssignmentLhsData AssignmentLhsData::KeyedProperty(hermes::Value* object,
                                                   hermes::Value* key) {
  return AssignmentLhsData(KEYED_PROPERTY, nullptr, {}, object, key, nullptr,
                           nullptr);
}
// static
AssignmentLhsData AssignmentLhsData::NamedSuperProperty(
    const hermes::CallInst::ArgumentList& super_property_args) {
  return AssignmentLhsData(NAMED_SUPER_PROPERTY, nullptr, super_property_args,
                           nullptr, nullptr, nullptr, nullptr);
}
// static
AssignmentLhsData AssignmentLhsData::KeyedSuperProperty(
    const hermes::CallInst::ArgumentList& super_property_args) {
  return AssignmentLhsData(KEYED_SUPER_PROPERTY, nullptr, super_property_args,
                           nullptr, nullptr, nullptr, nullptr);
}
// static
AssignmentLhsData AssignmentLhsData::PrivateMethodOrAccessor(
    AssignType type, Property* property, hermes::Value* object,
    hermes::Value* key) {
  return AssignmentLhsData(type, property, {}, object, key, nullptr, nullptr);
}
// static
AssignmentLhsData AssignmentLhsData::PrivateDebugEvaluate(
    AssignType type, Property* property, hermes::Value* object) {
  return AssignmentLhsData(type, property, {}, object, nullptr, nullptr,
                           nullptr);
}

class GotoLabel;
class SurroundingTry;

struct IteratorRecord {
  /// Storage for the iterator or the index.
  /// Set to undefined if iteration is complete.
  hermes::AllocStackInst* iterStorage;
  /// If this is an array being iterated by index, stores the source.
  /// Otherwise, stores the `iterator.next` method.
  hermes::AllocStackInst* sourceOrNext;
  /// Value of the element accessed by the iterator.
  hermes::AllocStackInst* value;
  /// A boolean value indicating whether the iteration ends.
  hermes::AllocStackInst* done;
};

struct SharedExceptionHandler {
  /// Stack location to store the exception if it occurs.
  hermes::AllocStackInst* exc = nullptr;
  /// Branch to this block if an exception occurs.
  hermes::BasicBlock* exceptionBlock = nullptr;
  /// Set to true if at least one try/catch block was needed and emitted.
  bool emittedTry = false;
};

class FunctionIRTranslator final {
 private:
  class CompileTimeScopeGuard;

 public:
  explicit FunctionIRTranslator(
      i::Isolate* isolate, i::ParseInfo& parse_info, Handle<i::Script> script,
      std::shared_ptr<hermes::Context> context, hermes::Module& module,
      hermes::IRBuilder& builder, EagerInnerLiteralsType* eager_inner_literals,
      FunctionLiteral* literal, hermes::Function* curr_func,
      NameTable& nameTable, bool isNewBE, bool enable_log = false)
      : local_isolate_(isolate->AsLocalIsolate()),
        zone_(isolate->allocator(), ZONE_NAME),
        ast_factory_(&zone_, isolate->ast_string_constants(),
                     i::HashSeed(isolate)),
        info_(&zone_, &parse_info, literal),
        ast_string_constants_(parse_info.ast_string_constants()),
        closure_scope_(info_.scope()),
        current_scope_(info_.scope()),
        eager_inner_literals_(eager_inner_literals),
        script_(script),
        context_(context),
        module_(module),
        builder_(builder),
        anonymousIDs_(context->getStringTable()),
        curr_func_(curr_func),
        nameTable_(nameTable),
        isNewBE_(isNewBE),
        enable_log_(enable_log) {}

  void Run(uintptr_t stack_limit);
  // todo: remove
  template <typename IsolateT>
  Handle<BytecodeArray> FinalizeBytecode(IsolateT* isolate,
                                         Handle<Script> script) {}
  template <typename IsolateT>
  Handle<ByteArray> FinalizeSourcePositionTable(IsolateT* isolate) {}

  std::optional<std::string> GetFunctionLiteralUtf8Name(FunctionLiteral* func);

#ifdef DEBUG
  int CheckBytecodeMatches(BytecodeArray bytecode);
#endif

#define DECLARE_VISIT(type) hermes::Value* Gen##type(type* node);
  EXPRESSION_NODE_LIST(DECLARE_VISIT)
#undef DECLARE_VISIT

#define DECLARE_VISIT(type) void Gen##type(type* node);
  DECLARATION_NODE_LIST(DECLARE_VISIT)
  STATEMENT_NODE_LIST(DECLARE_VISIT)
#undef DECLARE_VISIT

  void GenerateAsyncFunctionBody();
  void GenerateAsyncGeneratorFunctionBody();

  // Visiting function for declarations list and statements are overriden.
  void GenModuleDeclarations(Declaration::List* declarations);
  void GenGlobalDeclarations(Declaration::List* declarations);
  void GenDeclarations(Declaration::List* declarations);
  void GenStatements(const ZonePtrList<Statement>* statements, int start = 0);
  void GenDeclaration(Declaration* node);
  hermes::Value* GenExpression(Expression* node);
  void GenStatement(Statement* node);

  void BuildInstanceMemberInitialization(hermes::Value* constructor,
                                         hermes::Value* instance);

  hermes::Value* BuildStoreGlobal(Variable* target, hermes::Value* variable);
  hermes::Value* GenArithmeticExpression(BinaryOperation* binop);

  void GenExpressionBranch(Expression* expr, hermes::BasicBlock* onTrue,
                           hermes::BasicBlock* onFalse,
                           hermes::BasicBlock* onNullish);
  hermes::Value* GenLogicalAssignmentExpr(hermes::Value* lhs,
                                          const AssignmentLhsData& lhs_data,
                                          CompoundAssignment* expr);
  void GenLogicalExpressionBranch(BinaryOperation* logical,
                                  hermes::BasicBlock* onTrue,
                                  hermes::BasicBlock* onFalse,
                                  hermes::BasicBlock* onNullish);

  void FillArrayWithIterator(IteratorRecord& iteratorRecord,
                             hermes::Value* array,
                             hermes::AllocStackInst* index);

  hermes::Value* GenArrayFromElements(const ZonePtrList<Expression>& list,
                                      ArrayLiteral* expr = nullptr);

  hermes::Value* GenPropertyLoad(hermes::Value* obj, Property* expr);
  hermes::Value* GenNamedSuperPropertyLoad(Property* expr,
                                           hermes::Value* receiver);
  hermes::Value* GenKeyedSuperPropertyLoad(Property* expr,
                                           hermes::Value* receiver);
  hermes::Value* GenPropertyDelete(hermes::Value* obj, Property* expr);

  hermes::Value* GenObjectExpr(ObjectLiteral* expr);

  void GenForWhileLoops(IterationStatement* stmt, Statement* init,
                        Expression* preTest, Expression* postTest,
                        Statement* update, Statement* body);

  void BuildVariableInit(hermes::Value* object, Variable* target,
                         HoleCheckMode hole_check_mode);
  void BuildVariableAssignment(hermes::Value* object, Variable* target,
                               Token::Value op, HoleCheckMode hole_check_mode);
  void BuildAssignment(hermes::Value* object, const AssignmentLhsData& lhs_data,
                       Token::Value op);
  void BuildAssignmentWithHandler(Expression* lhs,
                                  const AssignmentLhsData& lhs_data,
                                  hermes::Value* value,
                                  SharedExceptionHandler* handler,
                                  Token::Value op);
  void BuildArrayLiteralAssignment(hermes::Value* object, Token::Value op,
                                   ArrayLiteral* array_literal);
  void BuildObjectLiteralAssignment(hermes::Value* object, Token::Value op,
                                    ObjectLiteral* objectLiteral);

  hermes::Value* GenNot(UnaryOperation* expr);
  hermes::Value* GenDelete(UnaryOperation* unary);

  hermes::Value* GenNaryArithmeticExpression(NaryOperation* expr);

  hermes::Value* GenForTypeOfValue(Expression* expr);

  hermes::Value* GenTypeOf(UnaryOperation* expr);

  hermes::Instruction* emitStore(hermes::Value* storedValue, hermes::Value* ptr,
                                 bool declInit);

  AssignmentLhsData PrepareAssignmentLhs(Expression* lhs);

  void BuildGetAndCheckSuperConstructor(
      hermes::Value* this_function, hermes::Value* new_target,
      hermes::AllocStackInst* constructor_slot,
      hermes::BasicBlock* super_ctor_call_done);
  hermes::Value* BuildCallSuper(Call* expr);
  void BuildInstanceInitializationAfterSuperCall(hermes::Value* this_function,
                                                 hermes::Value* instance);
  void BuildPrivateBrandInitialization(hermes::Value* receiver,
                                       Variable* brand);
  void BuildPrivateBrandCheck(Property* property, hermes::Value* object);
  hermes::Value* BuildPrivateMethodIn(Variable* private_name,
                                      Expression* object_expression);
  hermes::Value* BuildPrivateGetterAccess(hermes::Value* object,
                                          hermes::Value* key);
  void BuildPrivateSetterAccess(hermes::Value* object, hermes::Value* key,
                                hermes::Value* value);
  void BuildInvalidPropertyAccess(MessageTemplate tmpl, Property* property);
  hermes::Value* BuildLoadPropertyKey(LiteralProperty* property);
  void BuildClassProperty(ClassLiteral::Property* property);

  void GenArgumentsObject(Variable* variable);
  void GenRestArgumentsArray(Variable* rest);
  void GenNewTargetVariable(Variable* variable);
  void GenThisFunctionVariable(Variable* variable, hermes::Value* value);

  /// If the initializer \p init is nullptr, just return \p value.
  /// Otherwise emit code to check whether \p value equals \c undefined, and
  /// evaluate and return the initializer in that case.
  hermes::Value* emitOptionalInitialization(hermes::Value* value,
                                            Expression* init);

  /// Generate code for assigning to the "rest" property in an object
  /// destructuring pattern. \p excludedItems is a list of the keys that have
  /// been destructured so far, so they can be excluded from the rest property.
  void emitRestProperty(
      Expression* rest,
      const llvh::SmallVectorImpl<hermes::Value*>& excludedItems,
      hermes::Value* source, Token::Value op);

  template <typename EB, typename EF, typename EH>
  hermes::BasicBlock* emitTryCatchScaffolding(hermes::BasicBlock* nextBlock,
                                              EB emitBody, EF emitNormalCleanup,
                                              EH emitHandler);

  /// Emit an operation guarded by try with a shared exception handler.
  template <typename EB>
  void emitTryWithSharedHandler(SharedExceptionHandler* handler, EB emitBody);

  /// Emit the IteratorInitInst for \p obj, which allows us to iterate arrays
  /// efficiently.
  IteratorRecord emitIteratorInit(hermes::Value* obj,
                                  bool needInitDoneFalse = true,
                                  bool isAsync = false);

  /// Emit the IteratorAdvanceInst for \p iteratorRecord, which may alter the
  /// iteratorRecord to indicate completion.
  hermes::Value* emitIteratorAdvance(IteratorRecord iteratorRecord,
                                     bool isAsync = false) {
    return builder_.createIteratorAdvanceInst(
        iteratorRecord.done, iteratorRecord.iterStorage,
        iteratorRecord.sourceOrNext, isAsync);
  }

  /// Emit the IteratorFinishInst, which will close the iterator if it was
  /// opened by IteratorInitInst.
  /// \param ignoreInnerException ignore any exceptions thrown by `.return()`.
  hermes::Value* emitIteratorFinish(IteratorRecord iteratorRecord,
                                    bool ignoreInnerException,
                                    bool isAsync = false) {
    hermes::Value* result = nullptr;
    // emit a try catch to ignore exception
    // todo(hh): bytecode lower use ignoreInnerException as well, check if it is
    // necessary
    if (ignoreInnerException) {
      auto* nextBlock = emitTryCatchScaffolding(
          nullptr,
          // emitBody.
          [this, &iteratorRecord, ignoreInnerException, &result, &isAsync]() {
            // Generate IR for the body of Try
            result = builder_.createIteratorFinishInst(
                iteratorRecord.done, iteratorRecord.iterStorage,
                ignoreInnerException, isAsync);
          },
          // emitNormalCleanup.
          []() {},
          // emitHandler.
          [this](hermes::BasicBlock* nextBlock, hermes::TryStartInst* inst) {
            builder_.createCatchInst(inst->getContext());
            builder_.createBranchInst(nextBlock);
          });
      builder_.setInsertionBlock(nextBlock);
    } else {
      result = builder_.createIteratorFinishInst(iteratorRecord.done,
                                                 iteratorRecord.iterStorage,
                                                 ignoreInnerException, isAsync);
    }
    return result;
  }

  /// Emit the IteratorRestInst, which will get spread array from iterator and
  /// close the iterator.
  hermes::Value* emitIteratorRest(IteratorRecord iteratorRecord) {
    return builder_.createIteratorRestInst(iteratorRecord.done,
                                           iteratorRecord.iterStorage,
                                           iteratorRecord.sourceOrNext);
  }

  /// @return Maybe nullptr.
  hermes::Value* GenLogicalOrSubExpression(Expression* expr,
                                           hermes::BasicBlock* exitBlock,
                                           hermes::AllocStackInst* temp);

  /// @return Maybe nullptr.
  hermes::Value* GenLogicalAndSubExpression(Expression* expr,
                                            hermes::BasicBlock* exitBlock,
                                            hermes::AllocStackInst* temp);

  /// @return Maybe nullptr.
  hermes::Value* GenNullishSubExpression(Expression* expr,
                                         hermes::BasicBlock* exitBlock,
                                         hermes::AllocStackInst* temp);

  hermes::Value* GenYield(hermes::Value* value);
  hermes::Value* GenYieldStarExpr(hermes::Value* value);
  void GenFinallyBeforeControlChange(
      SurroundingTry* sourceTry, SurroundingTry* targetTry,
      ControlFlowChange cfc, hermes::BasicBlock* continueTarget = nullptr);
  hermes::Identifier GenAnonymousLabelName(llvh::StringRef hint) {
    return anonymousIDs_.next(hint);
  }
  hermes::Value* BuildVariableLoad(
      VariableProxy* proxy, TypeofMode typeof_mode = TypeofMode::kNotInside);
  hermes::Value* BuildVariableLoad(
      Variable* var, HoleCheckMode mod,
      TypeofMode typeof_mode = TypeofMode::kNotInside);

  hermes::Value* BuildThrowIfHole(hermes::Value* checkedValue,
                                  Variable* variable);
  void BuildHoleCheckForVariableAssignment(hermes::Value* checkedValue,
                                           Variable* variable);

  // TODO:(wwq) Implement it for removing redundant hole checks.
  // void RememberHoleCheckInCurrentBlock(Variable* variable);
  bool VariableNeedsHoleCheckInCurrentBlock(Variable* variable,
                                            HoleCheckMode hole_check_mode);

  // If converted, the returned `StringRef` has same lifecycle with `out` string
  llvh::StringRef ToStringRef(const AstRawString* str, std::string& out) {
    // v8 parser has a different encoding for non-ascii string with hermes
    // parser, convert encoding if needed.
    // TODO:(wwq) Converting to UTF8 can be avoided once hermesIR=>pbc is
    // enabled.
    bool converted = EncodingUtils::convertToUTF8(str, out);
    llvh::StringRef ref;
    if (converted) {
      ref = llvh::StringRef(out.c_str(), out.size());
    } else {
      ref = llvh::StringRef(reinterpret_cast<const char*>(str->raw_data()),
                            str->byte_length());
    }
    return ref;
  }

  hermes::Identifier getIdentifierFromAscii(const char* str) {
    return context_->getIdentifier(str);
  }

  hermes::Identifier getIdentifierNoConversion(const char* str,
                                               size_t byte_length) {
    return context_->getIdentifier(llvh::StringRef(str, byte_length));
  }

  hermes::Identifier getIdentifierFromAstRaw(const AstRawString* str) {
    std::string out;
    llvh::StringRef ref = ToStringRef(str, out);
    return context_->getIdentifier(ref);
  }

  hermes::Identifier getIdentifierFromAstCons(
      const AstConsString* cons_string) {
    std::string out;
    EncodingUtils::convertToUTF8(cons_string, local_isolate_, out);
    return context_->getIdentifier(llvh::StringRef(out.c_str(), out.size()));
  }

  hermes::LiteralString* BuildLiteralString(const AstRawString* str) {
    return module_.getLiteralString(getIdentifierFromAstRaw(str));
  }

  hermes::LiteralString* BuildLiteralString(const AstConsString* str) {
    return module_.getLiteralString(getIdentifierFromAstCons(str));
  }

  void AddToEagerLiteralsIfEager(FunctionLiteral* literal,
                                 hermes::Function* func) {
    eager_inner_literals_->emplace_back(literal, func);
  }

  const AstRawString* GetLiteralString(Literal* key_literal) {
    uint32_t element_index = 0;
    if (key_literal->AsArrayIndex(&element_index)) {
      std::string key = std::to_string(element_index);
      return ast_factory_.GetOneByteString(key.c_str());
    }
    return key_literal->AsRawPropertyName();
  }

  hermes::LiteralArray* GenLiteralArray(const ZonePtrList<Expression>& list);
  hermes::LiteralArray* GenLiteralArray(ArrayLiteral* expr);
  hermes::LiteralObject* GenLiteralObject(ObjectLiteral* expr);
  hermes::Literal* GetBoilerplateValue(Expression* expr);

  bool RemainderOfBlockIsDead() const {
    return builder_.getInsertionBlock()->getTerminator();
  }

  hermes::ScopeDesc* CurrentScopeDesc() const { return currentIRScopeDesc_; }

  v8::internal::LanguageMode GetLanguageMode() const {
    // TODO:(wwq) Use scope-level (such as eval scope) strictness instead of
    // function-level strictness.
    return curr_func_->isStrictMode() ? v8::internal::LanguageMode::kStrict
                                      : v8::internal::LanguageMode::kSloppy;
  }

  LocalIsolate* local_isolate_;
  Zone zone_;
  i::AstValueFactory ast_factory_;
  UnoptimizedCompilationInfo info_;
  const AstStringConstants* ast_string_constants_;
  DeclarationScope* closure_scope_;
  Scope* current_scope_;
  hermes::ScopeDesc* currentIRScopeDesc_{};
  hermes::ScopeCreationInst* currentIRScope_{};
  // External vector of literals to be eagerly compiled.
  EagerInnerLiteralsType* eager_inner_literals_;
  i::Handle<i::Script> script_;
  uint32_t paramCountIncludingThis_ = 1;
  GotoLabel* goto_label_ = nullptr;
  SurroundingTry* surrounding_try_ = nullptr;

  // IR related
  std::shared_ptr<hermes::Context> context_;
  hermes::Module& module_;
  hermes::IRBuilder& builder_;
  hermes::InternalIdentifierMaker anonymousIDs_;

  hermes::Function* curr_func_;
  llvh::SmallVector<hermes::AllocStackInst*, 8> locals_;
  hermes::BasicBlock* check_return_block_ = nullptr;
  hermes::PhiInst* check_return_value_ = nullptr;

  NameTable& nameTable_;
  hermes::BasicBlock* top_optional_chain_null_block_ = nullptr;
  bool isNewBE_;  // todo(hh): it is unnecessary now
  bool enable_log_;
};

class OptionalChainScope {
 public:
  OptionalChainScope(FunctionIRTranslator* translator, hermes::Value* value)
      : translator_(translator),
        saved_null_block_(translator->top_optional_chain_null_block_) {
    hermes::IRBuilder& builder = translator_->builder_;
    hermes::BasicBlock* current_block = builder.getInsertionBlock();
    hermes::BasicBlock* null_block =
        builder.createBasicBlock(translator->curr_func_);
    done_block_ = builder.createBasicBlock(translator->curr_func_);
    translator->top_optional_chain_null_block_ = null_block;

    builder.setInsertionBlock(null_block);
    builder.createBranchInst(done_block_);

    builder.setInsertionBlock(done_block_);
    phi_ = builder.createPhiInst({value}, {null_block});

    builder.setInsertionBlock(current_block);
  }
  ~OptionalChainScope() {
    translator_->top_optional_chain_null_block_ = saved_null_block_;
    hermes::IRBuilder& builder = translator_->builder_;
    builder.createBranchInst(done_block_);

    builder.setInsertionBlock(done_block_);
  }

  hermes::PhiInst* Phi() { return phi_; }

 private:
  FunctionIRTranslator* translator_;
  hermes::BasicBlock* done_block_;
  hermes::BasicBlock* saved_null_block_;
  hermes::PhiInst* phi_;
};

class FunctionIRTranslator::CompileTimeScopeGuard final {
 public:
  CompileTimeScopeGuard(FunctionIRTranslator* trans, hermes::ScopeDesc* hscope)
      : translator_(trans),
        outerHScope_(translator_->currentIRScopeDesc_),
        outerHContext_(translator_->currentIRScope_) {
    if (hscope != nullptr) {
      DCHECK_EQ(hscope->getParent(), translator_->currentIRScopeDesc_);
      translator_->currentIRScope_ =
          translator_->builder_.createCreateInnerScopeInst(
              translator_->currentIRScope_, hscope);
      translator_->currentIRScopeDesc_ = hscope;
      translator_->builder_.setCurrentSourceLevelScope(hscope);
    }
  }
  ~CompileTimeScopeGuard() {
    if (outerHScope_ != translator_->currentIRScopeDesc_) {
      translator_->currentIRScopeDesc_ = outerHScope_;
      translator_->currentIRScope_ = outerHContext_;
      translator_->builder_.setCurrentSourceLevelScope(outerHScope_);
    }
  }
  CompileTimeScopeGuard(const CompileTimeScopeGuard&) = delete;
  CompileTimeScopeGuard& operator=(const CompileTimeScopeGuard&) = delete;

 private:
  FunctionIRTranslator* translator_;
  hermes::ScopeDesc* outerHScope_;
  hermes::ScopeCreationInst* outerHContext_;
};

// TODO: Support result type optimization.
class GotoLabel {
 public:
  GotoLabel(FunctionIRTranslator* translator, hermes::BasicBlock* breakTarget,
            hermes::BasicBlock* continueTarget, BreakableStatement* stmt)
      : translator_(translator),
        outer_(translator_->goto_label_),
        breakTarget_(breakTarget),
        continueTarget_(continueTarget),
        surroundingTry_(translator->surrounding_try_),
        stmt_(stmt) {
    translator_->goto_label_ = this;
  }

  ~GotoLabel() { translator_->goto_label_ = outer_; }

  GotoLabel(const GotoLabel&) = delete;
  GotoLabel& operator=(const GotoLabel&) = delete;

  hermes::BasicBlock* break_target() const { return breakTarget_; }
  hermes::BasicBlock* continue_target() const { return continueTarget_; }
  SurroundingTry* surrounding_try() const { return surroundingTry_; }
  BreakableStatement* statement() const { return stmt_; }
  GotoLabel* outer() const { return outer_; }

 private:
  FunctionIRTranslator* translator_;
  GotoLabel* outer_;
  hermes::BasicBlock* breakTarget_;
  hermes::BasicBlock* continueTarget_;
  /// A record on the stack defining the closest surrounding try/catch
  /// statement. We need this so we can call the finally handlers.
  SurroundingTry* surroundingTry_;
  BreakableStatement* stmt_;
};

class SurroundingTry {
 public:
  using GenFinalizerCB = std::function<void(
      AstNode*, ControlFlowChange cfc, hermes::BasicBlock* continueTarget)>;

  SurroundingTry(FunctionIRTranslator* translator, AstNode* node,
                 GenFinalizerCB genFinalizer = {})
      : translator_(translator),
        outer_(translator->surrounding_try_),
        node_(node),
        genFinalizer_(std::move(genFinalizer)) {
    translator_->surrounding_try_ = this;
  }

  ~SurroundingTry() { translator_->surrounding_try_ = outer_; }

  SurroundingTry(const SurroundingTry&) = delete;
  SurroundingTry& operator=(const SurroundingTry&) = delete;

  SurroundingTry* outer() const { return outer_; }

  bool has_finalizer() const { return static_cast<bool>(genFinalizer_); }

  void run_finalizer(ControlFlowChange cfc,
                     hermes::BasicBlock* continueTarget) {
    genFinalizer_(node_, cfc, continueTarget);
  }

 private:
  FunctionIRTranslator* translator_;
  SurroundingTry* outer_;
  AstNode* node_;
  GenFinalizerCB genFinalizer_{};
};
void FunctionIRTranslator::GenBlock(Block* stmt) {
  // set up the break block
  auto nextBB = builder_.createBasicBlock(curr_func_);

  if (stmt->scope() == nullptr) {
    GotoLabel thisLabel(this, nextBB, builder_.getInsertionBlock(), stmt);
    GenStatements(stmt->statements());
    builder_.createBranchInst(nextBB);
    builder_.setInsertionBlock(nextBB);
    return;
  }
  if (enableBlockScoping && stmt->scope()->NeedsContext()) {
    hermes::ScopeDesc* blockHScope =
        currentIRScopeDesc_->createInnerScope(hermes::ScopeDesc::kBlockScope);
    blockHScope->setFunction(curr_func_);
    CompileTimeScopeGuard guard(this, blockHScope);  // RAII
    GenDeclarations(stmt->scope()->declarations());
    GotoLabel thisLabel(this, nextBB, builder_.getInsertionBlock(), stmt);
    GenStatements(stmt->statements());
  } else {
    GenDeclarations(stmt->scope()->declarations());
    GotoLabel thisLabel(this, nextBB, builder_.getInsertionBlock(), stmt);
    GenStatements(stmt->statements());
  }
  builder_.createBranchInst(nextBB);
  builder_.setInsertionBlock(nextBB);
}

void FunctionIRTranslator::GenVariableDeclaration(VariableDeclaration* decl) {
  Variable* variable = decl->var();
  // Unused variables don't need to be visited.
  if (!variable->is_used()) return;

  switch (variable->location()) {
    case VariableLocation::UNALLOCATED:
    case VariableLocation::MODULE:
      FATAL(__FUNCTION__);
    // local and parameter handled in prologue
    case VariableLocation::LOCAL: {
      // Support TDZ
      if (variable->binding_needs_init()) {
        builder_.createStoreStackInst(builder_.getLiteralEmpty(),
                                      locals_[variable->index()]);
      }
      break;
    }
    // local and parameter handled in prologue
    case VariableLocation::PARAMETER: {
      break;
    }
    case VariableLocation::REPL_GLOBAL:
      break;
      // REPL let's are stored in script contexts. They get initialized with the
      // hole the same way as normal context allocated variables.
    case VariableLocation::CONTEXT: {
      // context variable can be parameter
      if (nameTable_.find(variable)) {
        break;
      }
      VarDecl::Kind declKind = getVarDeclKind(variable);
      hermes::Identifier name = getIdentifierFromAstRaw(variable->raw_name());
      hermes::Variable* var =
          builder_.createVariable(CurrentScopeDesc(), declKind, name);
      hermes::Value* initValue = builder_.getLiteralUndefined();
      if (variable->binding_needs_init()) {
        initValue = builder_.getLiteralEmpty();
      }
      builder_.createStoreFrameInst(initValue, var, currentIRScope_);
      nameTable_.insertIntoScope(variable, var);
      break;
    }
    case VariableLocation::LOOKUP: {
#if 0
      DCHECK_EQ(VariableMode::kDynamic, variable->mode());
      DCHECK(!variable->binding_needs_init());

      Register name = register_allocator()->NewRegister();

      builder()
          ->LoadLiteral(variable->raw_name())
          .StoreAccumulatorInRegister(name)
          .CallRuntime(Runtime::kDeclareEvalVar, name);
#endif
      FATAL("eval not fully support yet");
      break;
    }
  }
}

void FunctionIRTranslator::GenFunctionDeclaration(FunctionDeclaration* decl) {
  // Global function declaration. V8 and hermes differ greatly.
  Variable* variable = decl->var();
  CHECK(variable->mode() == VariableMode::kLet ||
        variable->mode() == VariableMode::kVar ||
        variable->mode() == VariableMode::kDynamic);
  // Unused variables don't need to be visited.
  if (!variable->is_used()) return;

  hermes::Value* value = GenFunctionLiteral(decl->fun());
  auto* newClosure = llvh::cast<hermes::CreateFunctionInst>(value);
  hermes::Identifier functionName =
      newClosure->getFunctionCode()->getOriginalOrInferredName();

  // Create a property if global scope, variable otherwise.
  if (curr_func_->isGlobalScope()) {
    auto* globalProp = builder_.createGlobalObjectProperty(functionName, true);
    if (globalProp->isDeclared() || !curr_func_->isStrictMode()) {
      builder_.createStorePropertyInst(newClosure, builder_.getGlobalObject(),
                                       globalProp->getName());
    } else {
      builder_.createTryStoreGlobalPropertyInst(newClosure, globalProp);
    }
  }
  VarDecl::Kind declKind = getVarDeclKind(variable);
  // if (!Mod->getContext().getCodeGenerationSettings().enableTDZ) {
  //   // short-circuit all declarations to be Var. TDZ dies here.
  //   declKind = Variable::DeclKind::Var;
  // }
  if (variable->IsContextSlot()) {
    auto* var =
        builder_.createVariable(CurrentScopeDesc(), declKind, functionName);
    builder_.createStoreFrameInst(newClosure, var, currentIRScope_);
    nameTable_.insertIntoScope(variable, var);
  } else if (variable->IsStackLocal()) {
    builder_.createStoreStackInst(newClosure, locals_[variable->index()]);
  } else if (variable->IsParameter()) {
    builder_.createStoreFrameInst(newClosure, nameTable_.lookup(variable),
                                  currentIRScope_);
  } else if (variable->IsUnallocated()) {
    // Noting to do here
  } else {
    FATAL(__FUNCTION__);
  }
}

void FunctionIRTranslator::GenModuleDeclarations(Declaration::List* decls) {
  FATAL(__FUNCTION__);
}

void FunctionIRTranslator::GenGlobalDeclarations(Declaration::List* decls) {
  for (Declaration* decl : *decls) {
    Variable* var = decl->var();
    CHECK(var->is_used());
    if (var->location() == VariableLocation::UNALLOCATED) {
      // var or function.
      if (decl->IsFunctionDeclaration()) {
        GenFunctionDeclaration(static_cast<FunctionDeclaration*>(decl));
      } else if (decl->IsVariableDeclaration()) {
        // declare global var
        CHECK(curr_func_->isGlobalScope());
        hermes::Identifier name = getIdentifierFromAstRaw(var->raw_name());
        builder_.createGlobalObjectProperty(name, true);
      } else {
        FATAL(__FUNCTION__);
      }
    } else {
      // let or const. Handled in NewScriptContext.
      CHECK(decl->IsVariableDeclaration());
      CHECK(IsLexicalVariableMode(var->mode()));
      GenVariableDeclaration(static_cast<VariableDeclaration*>(decl));
    }
  }
}

#define GENERATE_GEN_CASE(NodeType) \
  case AstNode::k##NodeType:        \
    return Gen##NodeType(static_cast<NodeType*>(node));

#define GENERATE_GEN_FAILURE_CASE(NodeType) \
  case AstNode::k##NodeType:                \
    FATAL(__FUNCTION__);

#define GENERATE_STMT_GEN_SWITCH()               \
  switch (node->node_type()) {                   \
    STATEMENT_NODE_LIST(GENERATE_GEN_CASE)       \
    FAILURE_NODE_LIST(GENERATE_GEN_FAILURE_CASE) \
    default:                                     \
      FATAL(__FUNCTION__);                       \
  }

#define GENERATE_EXPR_GEN_SWITCH()               \
  switch (node->node_type()) {                   \
    EXPRESSION_NODE_LIST(GENERATE_GEN_CASE)      \
    FAILURE_NODE_LIST(GENERATE_GEN_FAILURE_CASE) \
    default:                                     \
      FATAL(__FUNCTION__);                       \
  }

#define GENERATE_DECL_GEN_SWITCH()               \
  switch (node->node_type()) {                   \
    DECLARATION_NODE_LIST(GENERATE_GEN_CASE)     \
    FAILURE_NODE_LIST(GENERATE_GEN_FAILURE_CASE) \
    default:                                     \
      FATAL(__FUNCTION__);                       \
  }

void FunctionIRTranslator::GenStatement(Statement* node) {
  GENERATE_STMT_GEN_SWITCH();
}

void FunctionIRTranslator::GenStatements(
    const ZonePtrList<Statement>* statements, int start) {
  for (int i = start; i < statements->length(); i++) {
    GenStatement(statements->at(i));
    if (RemainderOfBlockIsDead()) break;
  }
}

hermes::Value* FunctionIRTranslator::GenExpression(Expression* node) {
  GENERATE_EXPR_GEN_SWITCH();
}

void FunctionIRTranslator::GenDeclaration(Declaration* node) {
  GENERATE_DECL_GEN_SWITCH();
}

void FunctionIRTranslator::GenDeclarations(Declaration::List* declarations) {
  for (Declaration* node : *declarations) {
    GenDeclaration(node);
  }
}

void FunctionIRTranslator::GenExpressionStatement(ExpressionStatement* stmt) {
  GenExpression(stmt->expression());
}

void FunctionIRTranslator::GenEmptyStatement(EmptyStatement* stmt) {}

void FunctionIRTranslator::GenIfStatement(IfStatement* stmt) {
  if (stmt->condition()->ToBooleanIsTrue()) {
    // Generate then block unconditionally as always true.
    GenStatement(stmt->then_statement());
  } else if (stmt->condition()->ToBooleanIsFalse()) {
    // Generate else block unconditionally if it exists.
    GenStatement(stmt->else_statement());
  } else {
    auto then_block = builder_.createBasicBlock(curr_func_);
    auto else_block = builder_.createBasicBlock(curr_func_);
    auto continue_block = builder_.createBasicBlock(curr_func_);

    GenExpressionBranch(stmt->condition(), then_block, else_block, nullptr);

    // IRGen the Then:
    builder_.setInsertionBlock(then_block);
    GenStatement(stmt->then_statement());
    builder_.createBranchInst(continue_block);

    // IRGen the Else:
    builder_.setInsertionBlock(else_block);
    GenStatement(stmt->else_statement());

    builder_.createBranchInst(continue_block);
    builder_.setInsertionBlock(continue_block);
  }
}

void FunctionIRTranslator::GenSloppyBlockFunctionStatement(
    SloppyBlockFunctionStatement* stmt) {
  GenStatement(stmt->statement());
}

void FunctionIRTranslator::GenContinueStatement(ContinueStatement* stmt) {
  CHECK(goto_label_ != nullptr);
  auto* targetLabel = goto_label_;
  // find continue target block
  while (targetLabel->statement() != stmt->target()) {
    targetLabel = targetLabel->outer();
  }
  CHECK(targetLabel != nullptr);
  GenFinallyBeforeControlChange(
      surrounding_try_, targetLabel->surrounding_try(),
      ControlFlowChange::Continue, targetLabel->continue_target());
  builder_.createBranchInst(targetLabel->continue_target());
  auto newBlock = builder_.createBasicBlock(curr_func_);
  builder_.setInsertionBlock(newBlock);
}

void FunctionIRTranslator::GenBreakStatement(BreakStatement* stmt) {
  GotoLabel* label = goto_label_;
  while (label != nullptr && label->statement() != stmt->target()) {
    label = label->outer();
  }
  CHECK(label != nullptr);

  GenFinallyBeforeControlChange(surrounding_try_, label->surrounding_try(),
                                ControlFlowChange::Break);
  builder_.createBranchInst(label->break_target());
  auto newBlock = builder_.createBasicBlock(curr_func_);
  builder_.setInsertionBlock(newBlock);
}

void FunctionIRTranslator::GenReturnStatement(ReturnStatement* stmt) {
  hermes::Value* value = GenExpression(stmt->expression());
  GenFinallyBeforeControlChange(surrounding_try_, nullptr,
                                ControlFlowChange::Break);
  if (check_return_block_) {
    builder_.createBranchInst(check_return_block_);
    check_return_value_->addEntry(value, builder_.getInsertionBlock());
  } else {
    builder_.createReturnInst(value, stmt->is_async_return());
  }
  // Code that comes after 'return' is dead code. Let's create a new un-linked
  // basic block and keep IRGen in that block. The optimizer will clean things
  // up.
  builder_.setInsertionBlock(builder_.createBasicBlock(curr_func_));
}

void FunctionIRTranslator::GenWithStatement(WithStatement* stmt) {
  FATAL(__FUNCTION__);
}

namespace {
bool IsSmiLiteralSwitchCaseValue(Expression* expr) {
  if (expr->IsSmiLiteral() ||
      (expr->IsLiteral() && expr->AsLiteral()->IsNumber() &&
       expr->AsLiteral()->AsNumber() == 0.0)) {
    return true;
  }
#ifdef DEBUG
  else if (expr->IsLiteral() && expr->AsLiteral()->IsNumber()) {
    CHECK(!IsSmiDouble(expr->AsLiteral()->AsNumber()));
  }
#endif
  return false;
}

// Precondition: we called IsSmiLiteral to check this.
inline int ReduceToSmiSwitchCaseValue(Expression* expr) {
  if (V8_LIKELY(expr->IsSmiLiteral())) {
    return expr->AsLiteral()->AsSmiLiteral().value();
  } else {
    // Only the zero case is possible otherwise.
    CHECK(expr->IsLiteral() && expr->AsLiteral()->IsNumber() &&
          expr->AsLiteral()->AsNumber() == -0.0);
    return 0;
  }
}

struct SwitchInfo {
  static const int kDefaultNotFound = -1;

  std::map<int, CaseClause*> covered_cases;
  int default_case;

  SwitchInfo() { default_case = kDefaultNotFound; }

  bool DefaultExists() { return default_case != kDefaultNotFound; }
  bool CaseExists(int j) {
    return covered_cases.find(j) != covered_cases.end();
  }
  bool CaseExists(Expression* expr) {
    return IsSmiLiteralSwitchCaseValue(expr)
               ? CaseExists(ReduceToSmiSwitchCaseValue(expr))
               : false;
  }
  CaseClause* GetClause(int j) { return covered_cases[j]; }

  bool IsDuplicate(CaseClause* clause) {
    return IsSmiLiteralSwitchCaseValue(clause->label()) &&
           CaseExists(clause->label()) &&
           clause != GetClause(ReduceToSmiSwitchCaseValue(clause->label()));
  }
  int MinCase() {
    return covered_cases.empty() ? INT_MAX : covered_cases.begin()->first;
  }
  int MaxCase() {
    return covered_cases.empty() ? INT_MIN : covered_cases.rbegin()->first;
  }
  void Print() {
    std::cout << "Covered_cases: " << '\n';
    for (auto iter = covered_cases.begin(); iter != covered_cases.end();
         ++iter) {
      std::cout << iter->first << "->" << iter->second << '\n';
    }
    std::cout << "Default_case: " << default_case << '\n';
  }
};

// Is the range of Smi's small enough relative to number of cases?
inline bool IsSpreadAcceptable(int spread, int n_cases) {
  return spread < v8_flags.switch_table_spread_threshold * n_cases;
}

// Checks whether we should use a jump table to implement a switch operation.
bool IsSwitchOptimizable(SwitchStatement* stmt, SwitchInfo* info) {
  ZonePtrList<CaseClause>* cases = stmt->cases();

  for (int i = 0; i < cases->length(); ++i) {
    CaseClause* clause = cases->at(i);
    if (clause->is_default()) {
      continue;
    } else if (!clause->label()->IsLiteral()) {
      // Don't consider Smi cases after a non-literal, because we need to
      // evaluate the non-literal.
      break;
    } else if (IsSmiLiteralSwitchCaseValue(clause->label())) {
      int value = ReduceToSmiSwitchCaseValue(clause->label());
      info->covered_cases.insert({value, clause});
    }
  }

  // GCC also jump-table optimizes switch statements with 6 cases or more.
  if (static_cast<int>(info->covered_cases.size()) >=
      v8_flags.switch_table_min_cases) {
    // Due to case spread will be used as the size of jump-table,
    // we need to check if it doesn't overflow by casting its
    // min and max bounds to int64_t, and calculate if the difference is less
    // than or equal to INT_MAX.
    int64_t min = static_cast<int64_t>(info->MinCase());
    int64_t max = static_cast<int64_t>(info->MaxCase());
    int64_t spread = max - min + 1;

    CHECK_GT(spread, 0);

    // Check if casted spread is acceptable and doesn't overflow.
    if (spread <= INT_MAX &&
        IsSpreadAcceptable(static_cast<int>(spread), cases->length())) {
      return true;
    }
  }
  // Invariant- covered_cases has all cases and only cases that will go in the
  // jump table.
  info->covered_cases.clear();
  return false;
}

}  // namespace

void FunctionIRTranslator::GenSwitchStatement(SwitchStatement* stmt) {
  SwitchInfo info;
  bool use_jump_table = IsSwitchOptimizable(stmt, &info);
  (void)use_jump_table;
  // TODO: handle optimized switch case
  //       1. whether use a jump table to implement a switch case
  //       2. all cases constant
  //       3. feedback_slot & registers alloc opt

  // set up the exit block
  auto exitBB = builder_.createBasicBlock(curr_func_);
  // set up goto label
  GotoLabel thisLabel(this, exitBB, nullptr, stmt);
  // defaultBB is set up as exit block
  auto defaultBB = exitBB;

  // TODO:set up the goto labels
  // sequentially allocate a basic block for each cases
  ZonePtrList<CaseClause>* clauses = stmt->cases();
  hermes::Value* discriminator = GenExpression(stmt->tag());
  llvh::SmallVector<hermes::BasicBlock*, 8> caseBlocks;

  for (unsigned i = 0; i < clauses->length(); ++i) {
    CaseClause* cc = clauses->at(i);
    caseBlocks.push_back(builder_.createBasicBlock(curr_func_));
    if (!cc->is_default()) {
      auto* caseVal = GenExpression(cc->label());
      auto* pred = builder_.createBinaryOperatorInst(
          caseVal, discriminator,
          hermes::BinaryOperatorInst::OpKind::StrictlyEqualKind);
      auto elseBB = builder_.createBasicBlock(curr_func_);
      builder_.createCondBranchInst(pred, caseBlocks[i], elseBB);
      builder_.setInsertionBlock(elseBB);
    } else {
      // handle default case
      defaultBB = caseBlocks.back();
    }
  }
  builder_.createBranchInst(defaultBB);
  // generate case body
  bool isFirstCase = true;
  for (unsigned i = 0; i < clauses->length(); ++i) {
    CaseClause* cc = clauses->at(i);
    auto statements = cc->statements();
    if (!isFirstCase) {
      builder_.createBranchInst(caseBlocks[i]);
    }
    builder_.setInsertionBlock(caseBlocks[i]);
    for (auto statement_ : *statements) {
      GenStatement(statement_);
    }
    isFirstCase = false;
  }
  if (!isFirstCase) {
    builder_.createBranchInst(exitBB);
  }
  builder_.setInsertionBlock(exitBB);
}

void FunctionIRTranslator::GenDoWhileStatement(DoWhileStatement* stmt) {
  GenForWhileLoops(stmt, nullptr, nullptr, stmt->cond(), nullptr, stmt->body());
}
void FunctionIRTranslator::GenWhileStatement(WhileStatement* stmt) {
  GenForWhileLoops(stmt, nullptr, stmt->cond(), stmt->cond(), nullptr,
                   stmt->body());
}
void FunctionIRTranslator::GenForStatement(ForStatement* stmt) {
  GenForWhileLoops(stmt, stmt->init(), stmt->cond(), stmt->cond(), stmt->next(),
                   stmt->body());
}

void FunctionIRTranslator::GenForInStatement(ForInStatement* stmt) {
  auto* indexStorage =
      builder_.createAllocStackInst(GenAnonymousLabelName("index"));
  hermes::Value* object = GenExpression(stmt->subject());

  auto* exitBlock = builder_.createBasicBlock(curr_func_);
  auto* loopBlock = builder_.createBasicBlock(curr_func_);
  auto* nextBlock = builder_.createBasicBlock(curr_func_);
  auto* advanceBlock = builder_.createBasicBlock(curr_func_);
  auto* bodyBlock = builder_.createBasicBlock(curr_func_);

  // Initialize the goto labels.
  GotoLabel thisLabel(this, exitBlock, advanceBlock, stmt);

  auto* checkedObject =
      builder_.createForInInitInst(object, indexStorage, exitBlock, loopBlock);

  // loop
  {
    // break if loop done
    builder_.setInsertionBlock(loopBlock);
    builder_.createForInCondBreakInst(indexStorage, exitBlock, nextBlock);
    // get loop value, it will jump skip body if undefined
    builder_.setInsertionBlock(nextBlock);
    auto propertyStringRepr = builder_.createForInNextInst(
        checkedObject, indexStorage, advanceBlock, bodyBlock);

    // loop body
    {
      builder_.setInsertionBlock(bodyBlock);

      // The left hand side of For-In statements can be any lhs expression
      // ("PutValue"). Example:
      //  1. for (x.y in [1,2,3])
      //  2. for (x in [1,2,3])
      //  3. for (var x in [1,2,3])
      // See ES5 $12.6.4 "The for-in Statement"
      AssignmentLhsData lhs_data = PrepareAssignmentLhs(stmt->each());
      // The string property value of the current iteration is saved into this
      // variable.
      BuildAssignment(propertyStringRepr, lhs_data, Token::kAssign);

      GenStatement(stmt->body());
      builder_.createBranchInst(advanceBlock);
    }
    builder_.setInsertionBlock(advanceBlock);
    // index++ and do loop continue
    builder_.createForInContinueInst(indexStorage, loopBlock);
  }
  builder_.setInsertionBlock(exitBlock);
}

void FunctionIRTranslator::GenForOfStatement(ForOfStatement* stmt) {
  auto* getNextBlock = builder_.createBasicBlock(curr_func_);
  auto* bodyBlock = builder_.createBasicBlock(curr_func_);
  auto* exitBlock = builder_.createBasicBlock(curr_func_);

  // Initialize the goto labels.
  GotoLabel thisLabel(this, exitBlock, getNextBlock, stmt);

  auto* exprValue = GenExpression(stmt->subject());
  bool isAsync = stmt->type() == IteratorType::kAsync;
  // for-of use done only after IteratorAdvance
  const IteratorRecord iteratorRecord =
      emitIteratorInit(exprValue, false, isAsync);

  builder_.createBranchInst(getNextBlock);

  // Attempt to retrieve the next value. If iteration is complete, finish the
  // loop. This stays outside the SurroundingTry below because exceptions in
  // `.next()` should not call `.return()` on the iterator.
  builder_.setInsertionBlock(getNextBlock);
  hermes::Value* nextValue = emitIteratorAdvance(iteratorRecord, isAsync);
  hermes::Value* done = builder_.createLoadStackInst(iteratorRecord.done);
  builder_.createCondBranchInst(done, exitBlock, bodyBlock);

  builder_.setInsertionBlock(bodyBlock);

  emitTryCatchScaffolding(
      getNextBlock,
      // emitBody.
      [this, stmt, nextValue, &iteratorRecord, getNextBlock, &isAsync]() {
        // Generate IR for the body of Try
        SurroundingTry thisTry{this, stmt,
                               [this, &iteratorRecord, getNextBlock, &isAsync](
                                   AstNode*, ControlFlowChange cfc,
                                   hermes::BasicBlock* continueTarget) {
                                 // Only emit the iteratorFinish if this is a
                                 // 'break' or if the target of the control flow
                                 // change is outside the current loop. If
                                 // continuing the existing loop, do not close
                                 // the iterator.
                                 if (cfc == ControlFlowChange::Break ||
                                     continueTarget != getNextBlock)
                                   emitIteratorFinish(iteratorRecord, false,
                                                      isAsync);
                               }};

        // Note: obtaining the value is not protected, but storing it is.
        BuildAssignment(nextValue, PrepareAssignmentLhs(stmt->each()),
                        Token::kAssign);

        GenStatement(stmt->body());
      },
      // emitNormalCleanup.
      []() {},
      // emitHandler.
      [this, &iteratorRecord, &isAsync](hermes::BasicBlock*,
                                        hermes::TryStartInst* inst) {
        auto* catchReg = builder_.createCatchInst(inst->getContext());
        emitIteratorFinish(iteratorRecord, true, isAsync);
        builder_.createThrowInst(catchReg);
      });

  builder_.setInsertionBlock(exitBlock);
}

void FunctionIRTranslator::GenTryCatchStatement(TryCatchStatement* stmt) {
  auto* nextBlock = emitTryCatchScaffolding(
      nullptr,
      // emitBody.
      [this, stmt]() {
        SurroundingTry thisTry(this, stmt);
        GenStatement(stmt->try_block());
      },
      // emitNormalCleanup.
      []() {
        // do nothing
      },
      // emitHandler.
      [this, stmt](hermes::BasicBlock* nextBlock, hermes::TryStartInst* inst) {
        auto* catchInst = builder_.createCatchInst(inst->getContext());
        if (stmt->scope()) {
          Variable* variable = stmt->scope()->catch_variable();
          VarDecl::Kind declKind = getVarDeclKind(variable);
          hermes::Identifier name =
              getIdentifierFromAstRaw(variable->raw_name());
          hermes::Variable* var =
              builder_.createVariable(CurrentScopeDesc(), declKind, name);

          nameTable_.insertIntoScope(variable, var);
          emitStore(catchInst, var, true);
        }

        GenStatement(stmt->catch_block());
        builder_.createBranchInst(nextBlock);
      });
  builder_.setInsertionBlock(nextBlock);
}

void FunctionIRTranslator::GenTryFinallyStatement(TryFinallyStatement* stmt) {
  auto* nextBlock = emitTryCatchScaffolding(
      nullptr,
      // emitBody.
      [this, stmt]() {
        SurroundingTry thisTry(
            this, stmt,
            [this](AstNode* node, ControlFlowChange, hermes::BasicBlock*) {
              GenStatement(
                  static_cast<TryFinallyStatement*>(node)->finally_block());
            });
        GenStatement(stmt->try_block());
      },
      // emitNormalCleanup.
      [this, stmt]() { GenStatement(stmt->finally_block()); },
      // emitHandler.
      [this, stmt](hermes::BasicBlock*, hermes::TryStartInst* inst) {
        // A finally block catches the exception and rethrows it.
        auto* catchReg = builder_.createCatchInst(inst->getContext());
        GenStatement(stmt->finally_block());
        builder_.createThrowInst(catchReg);
      });
  builder_.setInsertionBlock(nextBlock);
}

void FunctionIRTranslator::GenDebuggerStatement(DebuggerStatement* stmt) {
  FATAL(__FUNCTION__);
}

std::optional<std::string> FunctionIRTranslator::GetFunctionLiteralUtf8Name(
    FunctionLiteral* func) {
  const AstConsString* raw_name_ = func->raw_name();
  if (raw_name_ == nullptr) {
    return std::nullopt;
  }

  std::string out;
  EncodingUtils::convertToUTF8(raw_name_, local_isolate_, out);
  return out;
}

hermes::Value* FunctionIRTranslator::GenFunctionLiteral(FunctionLiteral* expr) {
  bool isStrict = expr->language_mode() == LanguageMode::kStrict;
  std::optional<std::string> name = GetFunctionLiteralUtf8Name(expr);
  hermes::Identifier functionName =
      name.has_value()
          ? getIdentifierNoConversion(name.value().c_str(), name.value().size())
          : hermes::Identifier::getFromPointer(nullptr);
  bool is_async = IsAsyncFunction(expr->kind());
  bool is_generator = IsGeneratorFunction(expr->kind());
  hermes::Function* newFunc = nullptr;
  hermes::Function::DefinitionKind kind =
      static_cast<hermes::Function::DefinitionKind>(expr->kind());
  if (is_async && is_generator) {
    newFunc = builder_.createAsyncGeneratorFunction(
        currentIRScopeDesc_->createInnerScope(),
        // TODO: name
        functionName, kind, isStrict,
        // TODO: SourceVisibility and SMRange
        hermes::SourceVisibility::Default, hermes::SMRange{},
        /* insertBefore */ nullptr);
  } else if (is_async) {
    newFunc = builder_.createAsyncFunction(
        currentIRScopeDesc_->createInnerScope(),
        // TODO: name
        functionName, kind, isStrict,
        // TODO: SourceVisibility and SMRange
        hermes::SourceVisibility::Default, hermes::SMRange{},
        /* insertBefore */ nullptr);
  } else if (is_generator) {
    newFunc = builder_.createGeneratorFunction(
        currentIRScopeDesc_->createInnerScope(),
        // TODO: name
        functionName, kind, isStrict,
        // TODO: SourceVisibility and SMRange
        hermes::SourceVisibility::Default, hermes::SMRange{},
        /* insertBefore */ nullptr);
  } else {
    newFunc = builder_.createFunction(
        currentIRScopeDesc_->createInnerScope(),  // TODO: name
        functionName, kind, isStrict,
        // TODO: SourceVisibility and SMRange
        hermes::SourceVisibility::Default, hermes::SMRange{},
        /* isGlobal */ false,
        /* insertBefore */ nullptr);
  }
  AddToEagerLiteralsIfEager(expr, newFunc);
  newFunc->setFunctionSyntaxKind(
      static_cast<portable::FunctionSyntaxKind>(expr->syntax_kind()));
  newFunc->setRequiresInstanceMembersInitializer(
      expr->requires_instance_members_initializer());
  return builder_.createCreateFunctionInst(newFunc, currentIRScope_);
}
hermes::Value* FunctionIRTranslator::GenClassLiteral(ClassLiteral* expr) {
  GenDeclarations(expr->scope()->declarations());

  // Create the class brand symbol and store it on the context during class
  // evaluation. This will be stored in the instance later in the constructor.
  // We do this early so that invalid access to private methods or accessors
  // in computed property keys throw.
  if (expr->scope()->brand() != nullptr) {
    hermes::Identifier class_name;
    if (expr->scope()->class_variable() != nullptr) {
      class_name =
          getIdentifierFromAstRaw(expr->scope()->class_variable()->raw_name());
    } else {
      class_name = GenAnonymousLabelName("class");
    }
    hermes::Value* brand = builder_.createCallBuiltinInst(
        hermes::BuiltinMethod::HermesBuiltin_PBCCreatePrivateBrandSymbol,
        {builder_.getLiteralString(class_name)});
    BuildVariableInit(brand, expr->scope()->brand(), HoleCheckMode::kElided);
  }

  std::unordered_map<
      Literal*, std::pair<ClassLiteral::Property*, ClassLiteral::Property*>,
      LiteralHasher, LiteralEqual>
      private_accessors;
  for (int i = 0; i < expr->private_members()->length(); i++) {
    ClassLiteral::Property* property = expr->private_members()->at(i);
    DCHECK(property->is_private());
    switch (property->kind()) {
      case ClassLiteral::Property::FIELD: {
        // Initialize the private field variables early.
        // Create the private name symbols for fields during class
        // evaluation and store them on the context. These will be
        // used as keys later during instance or static initialization.
        hermes::Value* private_name = BuildLiteralString(
            property->key()->AsLiteral()->AsRawPropertyName());
        hermes::Value* symbol = builder_.createCallBuiltinInst(
            hermes::BuiltinMethod::HermesBuiltin_PBCCreatePrivateNameSymbol,
            {private_name});
        DCHECK_NOT_NULL(property->private_name_var());
        BuildVariableInit(symbol, property->private_name_var(),
                          HoleCheckMode::kElided);
        break;
      }
      case ClassLiteral::Property::METHOD: {
        hermes::Value* value = GenExpression(property->value());
        BuildVariableInit(value, property->private_name_var(),
                          HoleCheckMode::kElided);
        break;
      }
      // Collect private accessors into a table to merge the creation of
      // those closures later.
      case ClassLiteral::Property::GETTER: {
        Literal* key = property->key()->AsLiteral();
        DCHECK_NULL(private_accessors[key].first);
        private_accessors[key].first = property;
        break;
      }
      case ClassLiteral::Property::SETTER: {
        Literal* key = property->key()->AsLiteral();
        DCHECK_NULL(private_accessors[key].second);
        private_accessors[key].second = property;
        break;
      }
      case ClassLiteral::Property::AUTO_ACCESSOR: {
        Variable* accessor_storage_private_name_var =
            property->auto_accessor_info()
                ->accessor_storage_name_proxy()
                ->var();
        // We reuse the already internalized
        // ".accessor-storage-<accessor_number>" strings that were defined in
        // the parser instead of the "<name>accessor storage" string from the
        // spec. The downsides are that is that these are the property names
        // that will show up in devtools and in error messages.
        // Additionally, a property can share a name with the corresponding
        // property of their parent class, i.e. for classes defined as
        // "class C {accessor x}" and "class D extends C {accessor y}",
        // if "d = new D()", then d.x and d.y will share the name
        // ".accessor-storage-0", (but a different private symbol).
        // TODO(42202709): Get to a resolution on how to handle this naming
        // issue before shipping the feature.
        hermes::Value* accessor_storage_private_name =
            BuildLiteralString(accessor_storage_private_name_var->raw_name());
        hermes::Value* symbol = builder_.createCallBuiltinInst(
            hermes::BuiltinMethod::HermesBuiltin_PBCCreatePrivateNameSymbol,
            {accessor_storage_private_name});
        BuildVariableInit(symbol, accessor_storage_private_name_var,
                          HoleCheckMode::kElided);
        auto accessor_pair = &private_accessors[property->key()->AsLiteral()];
        DCHECK_NULL(accessor_pair->first);
        accessor_pair->first = property;
        DCHECK_NULL(accessor_pair->second);
        accessor_pair->second = property;
        break;
      }
      default:
        UNREACHABLE();
    }
  }

  // Define private accessors, using only a single call to the runtime for
  // each pair of corresponding getters and setters, in the order the first
  // component is declared.
  for (auto& accessors : private_accessors) {
    hermes::CallInst::ArgumentList accessor_pair = {};
    ClassLiteral::Property* getter = accessors.second.first;
    ClassLiteral::Property* setter = accessors.second.second;
    Variable* accessor_pair_var;
    if (getter && getter->kind() == ClassLiteral::Property::AUTO_ACCESSOR) {
      DCHECK_EQ(setter, getter);
      AutoAccessorInfo* auto_accessor_info = getter->auto_accessor_info();
      accessor_pair.emplace_back(
          GenFunctionLiteral(auto_accessor_info->generated_getter()));
      accessor_pair.emplace_back(
          GenFunctionLiteral(auto_accessor_info->generated_setter()));
      accessor_pair_var =
          auto_accessor_info->property_private_name_proxy()->var();
    } else {
      accessor_pair.emplace_back(getter ? GenExpression(getter->value())
                                        : builder_.getLiteralNull());
      accessor_pair.emplace_back(setter ? GenExpression(setter->value())
                                        : builder_.getLiteralNull());
      accessor_pair_var = getter != nullptr ? getter->private_name_var()
                                            : setter->private_name_var();
    }
    hermes::Value* accessors_value = builder_.createCallBuiltinInst(
        hermes::BuiltinMethod::HermesBuiltin_PBCCreatePrivateAccessors,
        accessor_pair);
    DCHECK_NOT_NULL(accessor_pair_var);
    BuildVariableInit(accessors_value, accessor_pair_var,
                      HoleCheckMode::kElided);
  }

  std::vector<hermes::LiteralClass::Property> prop;
  for (int i = 0; i < expr->public_members()->length(); i++) {
    ClassLiteral::Property* property = expr->public_members()->at(i);
    hermes::Value* key = nullptr;
    if (property->is_computed_name()) {
      key = builder_.getLiteralTheHole();
    } else {
      uint32_t index;
      if (property->key()->AsLiteral()->AsArrayIndex(&index)) {
        key = builder_.getLiteralNumber(index);
      } else {
        key = GenLiteral(property->key()->AsLiteral());
      }
    }
    hermes::LiteralClass::Property::Kind kind =
        getClassLiteralPropertyKind(property);
    prop.emplace_back(property->is_static(), property->is_private(),
                      property->is_computed_name(), kind, key);
  }

  // TODO: set class position when source position is fixed.
  hermes::Value* classLiteral = builder_.getLiteralClass(0, 0, prop);
  hermes::Value* classConstructor = GenFunctionLiteral(expr->constructor());
  hermes::Value* superClass = expr->extends() ? GenExpression(expr->extends())
                                              : builder_.getLiteralTheHole();
  hermes::CallInst::ArgumentList args = {classLiteral, classConstructor,
                                         superClass};
  // Create computed names and method values nodes to store into the literal.
  for (int i = 0; i < expr->public_members()->length(); i++) {
    ClassLiteral::Property* property = expr->public_members()->at(i);
    if (property->is_computed_name()) {
      hermes::Value* key = BuildLoadPropertyKey(property);
      args.emplace_back(key);
      if (property->is_static()) {
        // The static prototype property is read only. We handle the non
        // computed property name case in the parser. Since this is the only
        // case where we need to check for an own read only property we
        // special case this so we do not need to do this for every property.
        hermes::BasicBlock* errorBB = builder_.createBasicBlock(curr_func_);
        hermes::BasicBlock* doneBB = builder_.createBasicBlock(curr_func_);

        hermes::Value* prototype = builder_.getLiteralString(
            getIdentifierFromAstRaw(ast_string_constants_->prototype_string()));
        builder_.createCondBranchInst(
            builder_.createBinaryOperatorInst(
                key, prototype, getBinaryOperatorInstKind(Token::kEqStrict)),
            errorBB, doneBB);

        builder_.setInsertionBlock(errorBB);
        builder_.createCallBuiltinInst(
            hermes::BuiltinMethod::HermesBuiltin_PBCThrowStaticPrototypeError,
            {});
        // TODO: Bug fix in TOA, and replace to:
        // builder_.createUnreachableInst();
        builder_.createBranchInst(doneBB);

        builder_.setInsertionBlock(doneBB);
      }

      if (property->kind() == ClassLiteral::Property::FIELD) {
        DCHECK(!property->is_private());
        // Initialize field's name variable with the computed name.
        DCHECK_NOT_NULL(property->computed_name_var());
        BuildVariableInit(key, property->computed_name_var(),
                          HoleCheckMode::kElided);
      }
    }

    DCHECK(!property->is_private());

    if (property->kind() == ClassLiteral::Property::FIELD) {
      // We don't compute field's value here, but instead do it in the
      // initializer function.
      continue;
    }

    if (property->kind() == ClassLiteral::Property::AUTO_ACCESSOR) {
      Variable* accessor_storage_private_name_var =
          property->auto_accessor_info()->accessor_storage_name_proxy()->var();
      hermes::Value* accessor_storage_private_name =
          BuildLiteralString(accessor_storage_private_name_var->raw_name());
      hermes::Value* symbol = builder_.createCallBuiltinInst(
          hermes::BuiltinMethod::HermesBuiltin_PBCCreatePrivateNameSymbol,
          {accessor_storage_private_name});
      BuildVariableInit(symbol, accessor_storage_private_name_var,
                        HoleCheckMode::kElided);

      AutoAccessorInfo* auto_accessor_info = property->auto_accessor_info();
      hermes::Value* getter =
          GenFunctionLiteral(auto_accessor_info->generated_getter());
      args.emplace_back(getter);
      hermes::Value* setter =
          GenFunctionLiteral(auto_accessor_info->generated_setter());
      args.emplace_back(setter);
      continue;
    }

    args.emplace_back(GenExpression(property->value()));
  }

  hermes::Value* classTemplate = builder_.createCallBuiltinInst(
      hermes::BuiltinMethod::HermesBuiltin_PBCDefineClass, args);

  // Assign to the home object variable. Accumulator already contains the
  // prototype.
  Variable* home_object_variable = expr->home_object();
  if (home_object_variable != nullptr) {
    DCHECK(home_object_variable->is_used());
    DCHECK(home_object_variable->IsContextSlot());
    BuildVariableInit(classTemplate, home_object_variable,
                      HoleCheckMode::kElided);
  }
  Variable* static_home_object_variable = expr->static_home_object();
  if (static_home_object_variable != nullptr) {
    DCHECK(static_home_object_variable->is_used());
    DCHECK(static_home_object_variable->IsContextSlot());
    BuildVariableInit(classConstructor, static_home_object_variable,
                      HoleCheckMode::kElided);
  }

  // Assign to class variable.
  Variable* class_variable = expr->scope()->class_variable();
  if (class_variable != nullptr && class_variable->is_used()) {
    DCHECK(class_variable->IsStackLocal() || class_variable->IsContextSlot());
    BuildVariableInit(classConstructor, class_variable, HoleCheckMode::kElided);
  }

  if (expr->instance_members_initializer_function() != nullptr) {
    hermes::Value* value =
        GenFunctionLiteral(expr->instance_members_initializer_function());
    hermes::Value* prop =
        builder_.getLiteralSymbol(hermes::LiteralSymbol::ClassFieldsSymbol);
    builder_.createStorePropertyInst(value, classConstructor, prop);
  }

  if (expr->static_initializer() != nullptr) {
    // TODO(gsathya): This can be optimized away to be a part of the
    // class boilerplate in the future. The name argument can be
    // passed to the DefineClass runtime function and have it set
    // there.
    // TODO(v8:13451): Alternatively, port SetFunctionName to an ic so that we
    // can replace the runtime call to a dedicate bytecode here.
    // TODO: if (name.is_valid())
    hermes::Value* initializer = GenFunctionLiteral(expr->static_initializer());
    builder_.createCallInst(hermes::CallInst::kNoTextifiedCallee, initializer,
                            classConstructor, {},
                            hermes::CallInst::CallType::CallReceiverProperty);
  }
  return classConstructor;
}

void FunctionIRTranslator::BuildInstanceInitializationAfterSuperCall(
    hermes::Value* this_function, hermes::Value* instance) {
  // Explicit calls to the super constructor using super() perform an
  // implicit binding assignment to the 'this' variable.
  //
  // Default constructors don't need have to do the assignment because
  // 'this' isn't accessed in default constructors.
  if (!IsDefaultConstructor(info_.literal()->kind())) {
    BuildVariableAssignment(instance,
                            closure_scope_->GetReceiverScope()->receiver(),
                            Token::kInit, HoleCheckMode::kRequired);
  }

  // The constructor scope always needs ScopeInfo, so we are certain that
  // the first constructor scope found in the outer scope chain is the
  // scope that we are looking for for this super() call.
  // Note that this doesn't necessarily mean that the constructor needs
  // a context, if it doesn't this would get handled specially in
  // BuildPrivateBrandInitialization().
  DeclarationScope* constructor_scope = info_.scope()->GetConstructorScope();

  // We can rely on the class_scope_has_private_brand bit to tell if the
  // constructor needs private brand initialization, and if that's
  // the case we are certain that its outer class scope requires a context to
  // keep the brand variable, so we can just get the brand variable
  // from the outer scope.
  if (constructor_scope->class_scope_has_private_brand()) {
    DCHECK(constructor_scope->outer_scope()->is_class_scope());
    ClassScope* class_scope = constructor_scope->outer_scope()->AsClassScope();
    DCHECK_NOT_NULL(class_scope->brand());
    Variable* brand = class_scope->brand();
    BuildPrivateBrandInitialization(instance, brand);
  }

  // The derived constructor has the correct bit set always, so we
  // don't emit code to load and call the initializer if not
  // required.
  //
  // For the arrow function or eval case, we always emit code to load
  // and call the initializer.
  //
  // TODO(gsathya): In the future, we could tag nested arrow functions
  // or eval with the correct bit so that we do the load conditionally
  // if required.
  if (info_.literal()->requires_instance_members_initializer() ||
      !IsDerivedConstructor(info_.literal()->kind())) {
    BuildInstanceMemberInitialization(this_function, instance);
  }
}

void FunctionIRTranslator::BuildPrivateBrandInitialization(
    hermes::Value* receiver, Variable* brand) {
  hermes::Value* brand_symbol =
      BuildVariableLoad(brand, HoleCheckMode::kElided);
  // TODO: Fix class context.
  hermes::Value* context = builder_.createGetContext();
  builder_.createStoreOwnPropertyInst(context, receiver, brand_symbol,
                                      hermes::IRBuilder::PropEnumerable::No);
}

void FunctionIRTranslator::BuildPrivateBrandCheck(Property* property,
                                                  hermes::Value* object) {
  Variable* private_name = property->key()->AsVariableProxy()->var();
  DCHECK(IsPrivateMethodOrAccessorVariableMode(private_name->mode()));
  ClassScope* scope = private_name->scope()->AsClassScope();
  if (private_name->is_static()) {
    // For static private methods, the only valid receiver is the class.
    // Load the class constructor.
    if (scope->class_variable() == nullptr) {
      // TODO:
    } else {
      hermes::Value* class_variable =
          BuildVariableLoad(scope->class_variable(), HoleCheckMode::kElided);
      hermes::BasicBlock* succBB = builder_.createBasicBlock(curr_func_);
      hermes::BasicBlock* failedBB = builder_.createBasicBlock(curr_func_);
      builder_.createCondBranchInst(
          builder_.createTestReferenceEqualInst(class_variable, object), succBB,
          failedBB);

      builder_.setInsertionBlock(failedBB);
      hermes::Value* idx = builder_.getLiteralNumber(
          Smi::FromEnum(MessageTemplate::kInvalidPrivateBrandStatic).value());
      hermes::Identifier name =
          getIdentifierFromAstRaw(scope->class_variable()->raw_name());
      hermes::Value* error = builder_.createCallBuiltinInst(
          hermes::BuiltinMethod::HermesBuiltin_PBCNewTypeError,
          {idx, builder_.getLiteralString(name)});
      builder_.createThrowInst(error);

      builder_.setInsertionBlock(succBB);
    }
  } else {
    hermes::Value* brand =
        BuildVariableLoad(scope->brand(), HoleCheckMode::kElided);
    builder_.createLoadPropertyInst(object, brand);
  }
}

hermes::Value* FunctionIRTranslator::BuildPrivateMethodIn(
    Variable* private_name, Expression* object_expression) {
  DCHECK(IsPrivateMethodOrAccessorVariableMode(private_name->mode()));
  ClassScope* scope = private_name->scope()->AsClassScope();
  if (private_name->is_static()) {
    // For static private methods, "#privatemethod in ..." only returns true for
    // the class constructor.
    FATAL(__FUNCTION__);
  } else {
    hermes::Value* brand =
        BuildVariableLoad(scope->brand(), HoleCheckMode::kElided);
    hermes::Value* object = GenExpression(object_expression);
    return builder_.createBinaryOperatorInst(
        brand, object, getBinaryOperatorInstKind(Token::kIn));
  }
}

hermes::Value* FunctionIRTranslator::BuildPrivateGetterAccess(
    hermes::Value* object, hermes::Value* key) {
  hermes::Value* accessor = builder_.createCallBuiltinInst(
      hermes::BuiltinMethod::HermesBuiltin_PBCLoadPrivateGetter, {key});
  return builder_.createCallInst(
      hermes::CallInst::kNoTextifiedCallee, accessor, object, {},
      hermes::CallInst::CallType::CallReceiverProperty);
}

void FunctionIRTranslator::BuildPrivateSetterAccess(hermes::Value* object,
                                                    hermes::Value* key,
                                                    hermes::Value* value) {
  hermes::Value* accessor = builder_.createCallBuiltinInst(
      hermes::BuiltinMethod::HermesBuiltin_PBCLoadPrivateSetter, {key});
  builder_.createCallInst(hermes::CallInst::kNoTextifiedCallee, accessor,
                          object, {value},
                          hermes::CallInst::CallType::CallReceiverProperty);
  return;
}

void FunctionIRTranslator::BuildInvalidPropertyAccess(MessageTemplate tmpl,
                                                      Property* property) {
  hermes::Value* idx = builder_.getLiteralNumber(Smi::FromEnum(tmpl).value());
  hermes::Identifier name =
      getIdentifierFromAstRaw(property->key()->AsVariableProxy()->raw_name());
  hermes::Value* error = builder_.createCallBuiltinInst(
      hermes::BuiltinMethod::HermesBuiltin_PBCNewTypeError,
      {idx, builder_.getLiteralString(name)});
  builder_.createThrowInst(error);

  auto newBlock = builder_.createBasicBlock(curr_func_);
  builder_.setInsertionBlock(newBlock);
}

hermes::Value* FunctionIRTranslator::BuildLoadPropertyKey(
    LiteralProperty* property) {
  if (property->key()->IsStringLiteral()) {
    return BuildLiteralString(property->key()->AsLiteral()->AsRawString());
  } else {
    hermes::Value* key = GenExpression(property->key());
    return builder_.createSingleOperandInst<hermes::ToNameInst>(key);
  }
}

void FunctionIRTranslator::BuildClassProperty(
    ClassLiteral::Property* property) {
  hermes::Value* key = nullptr;

  // Private methods are not initialized in BuildClassProperty.
  DCHECK_IMPLIES(property->is_private(),
                 property->kind() == ClassLiteral::Property::FIELD ||
                     property->is_auto_accessor());

  bool is_literal_store =
      property->key()->IsPropertyName() && !property->is_computed_name() &&
      !property->is_private() && !property->is_auto_accessor();

  if (!is_literal_store) {
    if (property->is_auto_accessor()) {
      Variable* var =
          property->auto_accessor_info()->accessor_storage_name_proxy()->var();
      DCHECK_NOT_NULL(var);
      key = BuildVariableLoad(var, HoleCheckMode::kElided);
    } else if (property->is_computed_name()) {
      DCHECK_EQ(property->kind(), ClassLiteral::Property::FIELD);
      DCHECK(!property->is_private());
      Variable* var = property->computed_name_var();
      DCHECK_NOT_NULL(var);
      // The computed name is already evaluated and stored in a variable at
      // class definition time.
      key = BuildVariableLoad(var, HoleCheckMode::kElided);
    } else if (property->is_private()) {
      Variable* private_name_var = property->private_name_var();
      DCHECK_NOT_NULL(private_name_var);
      key = BuildVariableLoad(private_name_var, HoleCheckMode::kElided);
    } else {
      key = GenExpression(property->key());
    }
  }

  if (is_literal_store) {
    hermes::Value* value = GenExpression(property->value());
    key = GenLiteral(property->key()->AsLiteral());
    builder_.createStoreOwnPropertyInst(value, curr_func_->getThisParameter(),
                                        key,
                                        hermes::IRBuilder::PropEnumerable::Yes);
  } else {
    hermes::Value* value = nullptr;
    if (property->NeedsSetFunctionName()) {
      // Static class fields require the name property to be set on
      // the class, meaning we can't wait until the
      // DefineKeyedOwnProperty call later to set the name.
      if (property->value()->IsClassLiteral() &&
          property->value()->AsClassLiteral()->static_initializer() !=
              nullptr) {
        FATAL(__FUNCTION__);
      } else {
        value = GenExpression(property->value());
        // TODO: set kSetFunctionName flag in StoreOwnPropertyInst
      }
    } else {
      value = GenExpression(property->value());
    }
    builder_.createStoreOwnPropertyInst(value, curr_func_->getThisParameter(),
                                        key,
                                        hermes::IRBuilder::PropEnumerable::Yes);
    // TODO: remove when kSetFunctionName flag is setted.
    if (property->NeedsSetFunctionName()) {
      if (!property->value()->IsClassLiteral() ||
          property->value()->AsClassLiteral()->static_initializer() ==
              nullptr) {
        builder_.createCallBuiltinInst(
            hermes::BuiltinMethod::HermesBuiltin_PBCSetFunctionName,
            {value, key});
      }
    }
  }
}

void FunctionIRTranslator::GenInitializeClassMembersStatement(
    InitializeClassMembersStatement* stmt) {
  for (int i = 0; i < stmt->fields()->length(); ++i) {
    BuildClassProperty(stmt->fields()->at(i));
  }
}
void FunctionIRTranslator::GenInitializeClassStaticElementsStatement(
    InitializeClassStaticElementsStatement* stmt) {
  for (int i = 0; i < stmt->elements()->length(); ++i) {
    ClassLiteral::StaticElement* element = stmt->elements()->at(i);
    switch (element->kind()) {
      case ClassLiteral::StaticElement::PROPERTY:
        BuildClassProperty(element->property());
        break;
      case ClassLiteral::StaticElement::STATIC_BLOCK:
        GenBlock(element->static_block());
        break;
    }
  }
}
void FunctionIRTranslator::GenAutoAccessorGetterBody(
    AutoAccessorGetterBody* stmt) {
  hermes::Value* object = curr_func_->getThisParameter();
  hermes::Value* key =
      BuildVariableLoad(stmt->name_proxy()->var(), HoleCheckMode::kElided);
  hermes::Value* value = builder_.createLoadPropertyInst(object, key);
  builder_.createReturnInst(value);
}
void FunctionIRTranslator::GenAutoAccessorSetterBody(
    AutoAccessorSetterBody* stmt) {
  hermes::Value* object = curr_func_->getThisParameter();
  hermes::Value* key =
      BuildVariableLoad(stmt->name_proxy()->var(), HoleCheckMode::kElided);
  hermes::Value* value = curr_func_->getParameters()[0];
  builder_.createStorePropertyInst(value, object, key);
}
hermes::Value* FunctionIRTranslator::GenNativeFunctionLiteral(
    NativeFunctionLiteral* expr) {
  FATAL(__FUNCTION__);
}

hermes::Value* FunctionIRTranslator::GenConditionalChain(
    ConditionalChain* expr) {
  hermes::BasicBlock* endBB = builder_.createBasicBlock(curr_func_);
  hermes::PhiInst::ValueListType values;
  hermes::PhiInst::BasicBlockListType blocks;

  bool shouldVisitElseExpression = true;
  size_t length = expr->conditional_chain_length();

  auto emitBlockEnd = [this, &values, &blocks, endBB](hermes::Value* expr) {
    values.push_back(expr);
    blocks.push_back(builder_.getInsertionBlock());
    builder_.createBranchInst(endBB);
  };

  for (size_t i = 0; i < length; ++i) {
    if (expr->condition_at(i)->ToBooleanIsTrue()) {
      // Generate then block unconditionally as always true.
      shouldVisitElseExpression = false;
      emitBlockEnd(GenExpression(expr->then_expression_at(i)));
      break;
    } else if (expr->condition_at(i)->ToBooleanIsFalse()) {
      continue;
    } else {
      hermes::BasicBlock* bodyBB = builder_.createBasicBlock(curr_func_);
      hermes::BasicBlock* nextBB = builder_.createBasicBlock(curr_func_);
      builder_.createCondBranchInst(GenExpression(expr->condition_at(i)),
                                    bodyBB, nextBB);
      {
        builder_.setInsertionBlock(bodyBB);
        emitBlockEnd(GenExpression(expr->then_expression_at(i)));
      }
      builder_.setInsertionBlock(nextBB);
    }
  }

  if (shouldVisitElseExpression) {
    emitBlockEnd(GenExpression(expr->else_expression()));
  }

  builder_.setInsertionBlock(endBB);
  return builder_.createPhiInst(values, blocks);
}

hermes::Value* FunctionIRTranslator::GenConditional(Conditional* expr) {
  if (expr->condition()->ToBooleanIsTrue()) {
    return GenExpression(expr->then_expression());
  } else if (expr->condition()->ToBooleanIsFalse()) {
    // Generate else block unconditionally if it exists.
    return GenExpression(expr->else_expression());
  } else {
    hermes::PhiInst::ValueListType values;
    hermes::PhiInst::BasicBlockListType blocks;

    auto alternateBlock = builder_.createBasicBlock(curr_func_);
    auto consequentBlock = builder_.createBasicBlock(curr_func_);
    auto continueBlock = builder_.createBasicBlock(curr_func_);

    // Implement the ternary operator using control flow. We must use control
    // flow because the expressions may have side effects.
    GenExpressionBranch(expr->condition(), consequentBlock, alternateBlock,
                        nullptr);

    // The 'then' side:
    builder_.setInsertionBlock(consequentBlock);

    values.push_back(GenExpression(expr->then_expression()));
    blocks.push_back(builder_.getInsertionBlock());
    builder_.createBranchInst(continueBlock);

    // The 'else' side:
    builder_.setInsertionBlock(alternateBlock);
    values.push_back(GenExpression(expr->else_expression()));
    blocks.push_back(builder_.getInsertionBlock());
    builder_.createBranchInst(continueBlock);

    // Continue:
    builder_.setInsertionBlock(continueBlock);
    return builder_.createPhiInst(values, blocks);
  }
}

hermes::Value* FunctionIRTranslator::GenLiteral(Literal* expr) {
  switch (expr->type()) {
    case Literal::kSmi:
      return builder_.getLiteralNumber(expr->AsNumber());
    case Literal::kHeapNumber:
      return builder_.getLiteralNumber(expr->AsNumber());
    case Literal::kUndefined:
      return builder_.getLiteralUndefined();
    case Literal::kBoolean:
      return builder_.getLiteralBool(expr->ToBooleanIsTrue());
    case Literal::kNull:
      return builder_.getLiteralNull();
    case Literal::kTheHole:
      return builder_.getLiteralTheHole();  // todo(hh): empty?
    case Literal::kString:
      return BuildLiteralString(expr->AsRawString());
    case Literal::kConsString:
      return BuildLiteralString(expr->AsConsString());
    case Literal::kBigInt:
      // Need to add 'n' as hermes literal suffix.
      std::string bigint(expr->AsBigInt().c_str());
      bigint.push_back('n');
      // Bigint is always ascii-encoded.
      return builder_.getLiteralBigInt(
          getIdentifierFromAscii(bigint.c_str()).getUnderlyingPointer());
  }
}

hermes::Value* FunctionIRTranslator::GenRegExpLiteral(RegExpLiteral* expr) {
  int flags = expr->flags();
  hermes::Identifier pattern = getIdentifierFromAstRaw(expr->raw_pattern());
  std::vector<char> buf;
#define V(Lower, Camel, LowerCamel, Char, Bit) \
  if (flags & v8::RegExp::k##Camel) buf.push_back(Char);
  REGEXP_FLAG_LIST(V)
#undef V
  buf.push_back('\0');

  // Regexp flag is always ascii-encoded.
  // todo(hh): maybe use int type for flag is better
  hermes::Identifier flags_identifier = getIdentifierFromAscii(buf.data());
  return builder_.createRegExpInst(pattern, flags_identifier);
}

hermes::Value* FunctionIRTranslator::GenObjectLiteral(ObjectLiteral* expr) {
  expr->builder()->InitDepthAndFlags();

  Variable* home_object = expr->home_object();
  if (home_object != nullptr) {
    CHECK(home_object->is_used());
    CHECK(home_object->IsContextSlot());
  }
  hermes::Value* obj = GenObjectExpr(expr);
  if (home_object != nullptr) {
    VarDecl::Kind declKind = getVarDeclKind(home_object);
    hermes::Identifier name = getIdentifierFromAstRaw(home_object->raw_name());
    hermes::Variable* var =
        builder_.createVariable(CurrentScopeDesc(), declKind, name);
    builder_.createStoreFrameInst(obj, var, currentIRScope_);
    nameTable_.insertIntoScope(home_object, var);
  }
  return obj;
}

hermes::Value* FunctionIRTranslator::GenArrayLiteral(ArrayLiteral* expr) {
  expr->builder()->InitDepthAndFlags();
  return GenArrayFromElements(*expr->values(), expr);
}

hermes::Value* FunctionIRTranslator::GenVariableProxy(VariableProxy* proxy) {
  return BuildVariableLoad(proxy->var(), proxy->hole_check_mode());
}

bool FunctionIRTranslator::VariableNeedsHoleCheckInCurrentBlock(
    Variable* variable, HoleCheckMode hole_check_mode) {
  return hole_check_mode == HoleCheckMode::kRequired;
  // TODO:(wwq) Optimize hole check by cache.
  // && !variable->HasRememberedHoleCheck(hole_check_bitmap_);
}

hermes::Value* FunctionIRTranslator::BuildThrowIfHole(
    hermes::Value* checkedValue, Variable* variable) {
  if (variable->is_this()) {
    DCHECK(variable->mode() == VariableMode::kConst);
    return builder_.createThrowSuperNotCalledIfHoleInst(checkedValue);
  } else {
    hermes::Identifier name = getIdentifierFromAstRaw(variable->raw_name());
    const llvh::StringRef& nameRef = name.str();
    return builder_.createThrowIfEmptyInst(checkedValue, nameRef);
  }
}

void FunctionIRTranslator::BuildHoleCheckForVariableAssignment(
    hermes::Value* checkedValue, Variable* variable) {
  DCHECK(!IsPrivateMethodOrAccessorVariableMode(variable->mode()));
  if (variable->is_this()) {
    DCHECK(variable->mode() == VariableMode::kConst);
    // Perform an initialization check for 'this'. 'this' variable is the
    // only variable able to trigger bind operations outside the TDZ
    // via 'super' calls.
    //
    // Do not remember the hole check because this bytecode throws if 'this' is
    // *not* the hole, i.e. the opposite of the TDZ hole check.
    builder_.createThrowSuperAlreadyCalledIfNotHoleInst(checkedValue);
  } else {
    // Perform an initialization check for let/const declared variables.
    // E.g. let x = (x = 20); is not allowed.
    DCHECK(IsLexicalVariableMode(variable->mode()));
    BuildThrowIfHole(checkedValue, variable);
  }
}

hermes::Value* FunctionIRTranslator::BuildVariableLoad(VariableProxy* proxy,
                                                       TypeofMode typeof_mode) {
  return BuildVariableLoad(proxy->var(), proxy->hole_check_mode(), typeof_mode);
}

hermes::Value* FunctionIRTranslator::BuildVariableLoad(Variable* var,
                                                       HoleCheckMode mod,
                                                       TypeofMode typeof_mode) {
  switch (var->location()) {
    case VariableLocation::LOCAL: {
      auto* inst = builder_.createLoadStackInst(locals_[var->index()]);
      if (VariableNeedsHoleCheckInCurrentBlock(var, mod)) {
        return BuildThrowIfHole(inst, var);
      }
      return inst;
    }
    case VariableLocation::UNALLOCATED: {
      // The global identifier "undefined" is immutable. Everything
      // else could be reassigned. For performance, we do a pointer comparison
      // rather than checking if the raw_name is really "undefined".
      if (var->raw_name() == ast_string_constants_->undefined_string()) {
        return builder_.getLiteralUndefined();
      } else {
        hermes::LiteralString* name = BuildLiteralString(var->raw_name());
        if (typeof_mode == TypeofMode::kInside) {
          return builder_.createLoadPropertyInst(builder_.getGlobalObject(),
                                                 name);
        }
        return builder_.createTryLoadGlobalPropertyInst(name);
      }
      break;
    }
    case VariableLocation::PARAMETER:
    case VariableLocation::CONTEXT: {
      auto* inst =
          builder_.createLoadFrameInst(nameTable_.lookup(var), currentIRScope_);
      if (VariableNeedsHoleCheckInCurrentBlock(var, mod)) {
        return BuildThrowIfHole(inst, var);
      }
      return inst;
    }
    case VariableLocation::LOOKUP: {
      if (!isNewBE_) {
        FATAL("eval not fully support yet");
      }
      switch (var->mode()) {
        case VariableMode::kDynamicGlobal: {
          hermes::LiteralString* name = BuildLiteralString(var->raw_name());
          bool inTypeof = (typeof_mode == TypeofMode::kInside);
          // TODO:(wwq) Update current_scope_ using RAII,
          // see v8::internal::interpreter::BytecodeGenerator::CurrentScope
          return builder_.createLoadLookupGlobalInst(name, inTypeof,
                                                     currentIRScope_);
        }
        default: {
          // TODO:(wwq) Support more variable mode for LOOKUP
          UNREACHABLE();
        }
      }
      break;
    }
    // TODO: other case
    default:
      FATAL(__FUNCTION__);
  }
}

hermes::Value* FunctionIRTranslator::GenAssignment(Assignment* expr) {
  AssignmentLhsData lhs_data = PrepareAssignmentLhs(expr->target());
  hermes::Value* object = GenExpression(expr->value());
  BuildAssignment(object, lhs_data, expr->op());
  return object;
}

hermes::Value* FunctionIRTranslator::GenCompoundAssignment(
    CompoundAssignment* expr) {
  AssignmentLhsData lhs_data = PrepareAssignmentLhs(expr->target());
  hermes::Value* object = nullptr;

  // Evaluate the value and potentially handle compound assignments by loading
  // the left-hand side value and performing a binary operation.
  switch (lhs_data.assign_type()) {
    case NON_PROPERTY: {
      VariableProxy* proxy = expr->target()->AsVariableProxy();
      object = BuildVariableLoad(proxy->var(), proxy->hole_check_mode());
      break;
    }
    case NAMED_PROPERTY: {
      FATAL(__FUNCTION__);
#if 0
      BuildLoadNamedProperty(lhs_data.object_expr(), lhs_data.object(),
                             lhs_data.name());
#endif
      break;
    }
    case KEYED_PROPERTY: {
      object =
          builder_.createLoadPropertyInst(lhs_data.object(), lhs_data.key());
      break;
    }
    case NAMED_SUPER_PROPERTY: {
      object = builder_.createCallBuiltinInst(
          hermes::BuiltinMethod::HermesBuiltin_PBCLoadFromSuper,
          lhs_data.super_property_args());
      break;
    }
    case KEYED_SUPER_PROPERTY: {
      object = builder_.createCallBuiltinInst(
          hermes::BuiltinMethod::HermesBuiltin_PBCLoadKeyedFromSuper,
          lhs_data.super_property_args());
      break;
    }
    // BuildAssignment() will throw an error about the private method being
    // read-only.
    case PRIVATE_METHOD: {
      Property* property = lhs_data.expr()->AsProperty();
      BuildPrivateBrandCheck(property, lhs_data.object());
      object = lhs_data.key();
      break;
    }
    // For read-only properties, BuildAssignment() will throw an error about
    // the missing setter.
    case PRIVATE_GETTER_ONLY:
    case PRIVATE_GETTER_AND_SETTER: {
      Property* property = lhs_data.expr()->AsProperty();
      BuildPrivateBrandCheck(property, lhs_data.object());
      object = BuildPrivateGetterAccess(lhs_data.object(), lhs_data.key());
      break;
    }
    case PRIVATE_SETTER_ONLY: {
      // The property access is invalid, but if the brand check fails too, we
      // need to return the error from the brand check.
      Property* property = lhs_data.expr()->AsProperty();
      BuildPrivateBrandCheck(property, lhs_data.object());
      BuildInvalidPropertyAccess(MessageTemplate::kInvalidPrivateGetterAccess,
                                 lhs_data.expr()->AsProperty());
      break;
    }
#if 0
    case PRIVATE_DEBUG_DYNAMIC: {
      Property* property = lhs_data.expr()->AsProperty();
      BuildPrivateDebugDynamicGet(property, lhs_data.object());
      break;
    }
#endif
    default: {
      FATAL(__FUNCTION__);
      break;
    }
  }

  BinaryOperation* binop = expr->binary_operation();
  if (binop->op() >= Token::kNullish && binop->op() <= Token::kAnd) {
    object = GenLogicalAssignmentExpr(object, lhs_data, expr);
  } else if (expr->value()->IsSmiLiteral()) {
    // TODO: Optimized IR for SMI.
    object = builder_.createBinaryOperatorInst(
        object,
        builder_.getLiteralNumber(expr->value()->AsLiteral()->AsNumber()),
        getBinaryOperatorInstKind(binop->op()));
    BuildAssignment(object, lhs_data, expr->op());
  } else {
    hermes::Value* rhs = GenExpression(expr->value());
    object = builder_.createBinaryOperatorInst(
        object, rhs, getBinaryOperatorInstKind(binop->op()));
    BuildAssignment(object, lhs_data, expr->op());
  }
  return object;
}

static bool needCopyFinallyForYield(FunctionIRTranslator& trans) {
  bool copyFinally = false;
  SurroundingTry* sourceTry = trans.surrounding_try_;
  while (sourceTry != nullptr) {
    if (sourceTry->has_finalizer()) {
      copyFinally = true;
      break;
    }
    sourceTry = sourceTry->outer();
  }
  return copyFinally;
}

void FunctionIRTranslator::GenFinallyBeforeControlChange(
    SurroundingTry* sourceTry, SurroundingTry* targetTry, ControlFlowChange cfc,
    hermes::BasicBlock* continueTarget) {
  CHECK((cfc == ControlFlowChange::Break || continueTarget != nullptr) &&
        "Continue ControlFlowChange must have a target");
  // We walk the nested try statements starting from the source, until we reach
  // the target, generating the finally statements on the way.
  for (; sourceTry != targetTry; sourceTry = sourceTry->outer()) {
    CHECK(sourceTry && "invalid try chain");

    // Emit an end of the try statement.
    auto* tryEndBlock = builder_.createBasicBlock(curr_func_);
    builder_.createBranchInst(tryEndBlock);
    builder_.setInsertionBlock(tryEndBlock);

    // Make sure we use the correct debug location for tryEndInst.
    // if (sourceTry->tryEndLoc.isValid()) {
    //   hermes::IRBuilder::ScopedLocationChange slc(
    //       Builder, sourceTry->tryEndLoc);
    //   Builder.createTryEndInst();
    // } else {
    builder_.createTryEndInst();
    // }

    if (sourceTry->has_finalizer()) {
      // Recreate the state of the try stack on entrance to the finally block.
      llvh::SaveAndRestore<SurroundingTry*> sr{surrounding_try_,
                                               sourceTry->outer()};
      sourceTry->run_finalizer(cfc, continueTarget);
    }
  }
}

hermes::Value* FunctionIRTranslator::GenYield(hermes::Value* value) {
  bool copyFinally = needCopyFinallyForYield(*this);

  hermes::Value* yieldInst = nullptr;
  if (copyFinally) {
    auto* bb = builder_.getInsertionBlock();
    auto* nextBB = builder_.createBasicBlock(bb->getParent());
    auto* finallyBB = builder_.createBasicBlock(bb->getParent());

    auto* inputTaker =
        builder_.createAllocStackInst(GenAnonymousLabelName("inputTaker"));
    auto* isReturnTaker =
        builder_.createAllocStackInst(GenAnonymousLabelName("isReturnTaker"));

    if (isNewBE_) {
      yieldInst = builder_.createYieldFinallyInst(
          value, inputTaker, isReturnTaker, nextBB, finallyBB);
    } else {
      yieldInst = builder_.createYieldInst(value, inputTaker, isReturnTaker);
      builder_.createCondBranchInst(builder_.createLoadStackInst(isReturnTaker),
                                    finallyBB, nextBB);
    }

    builder_.setInsertionBlock(finallyBB);
    GenFinallyBeforeControlChange(surrounding_try_, nullptr,
                                  ControlFlowChange::Break);
    builder_.createReturnInst(builder_.createLoadStackInst(inputTaker),
                              IsAsyncFunction(info_.literal()->kind()));

    builder_.setInsertionBlock(nextBB);
  } else {
    auto* inputTaker =
        builder_.createAllocStackInst(GenAnonymousLabelName("inputTaker"));
    auto* isReturnTaker =
        builder_.createAllocStackInst(GenAnonymousLabelName("isReturnTaker"));
    yieldInst = builder_.createYieldInst(value, inputTaker, isReturnTaker);
  }
  return yieldInst;
}

hermes::Value* FunctionIRTranslator::GenYield(Yield* expr) {
  return GenYield(GenExpression(expr->expression()));
}

hermes::Value* FunctionIRTranslator::GenYieldStarExpr(hermes::Value* value) {
  bool copyFinally = needCopyFinallyForYield(*this);
  hermes::Value* yieldStarInst = nullptr;
  if (copyFinally) {
    auto* bb = builder_.getInsertionBlock();
    auto* nextBB = builder_.createBasicBlock(bb->getParent());
    auto* finallyBB = builder_.createBasicBlock(bb->getParent());

    auto* inputTaker =
        builder_.createAllocStackInst(GenAnonymousLabelName("inputTaker"));
    auto* isReturnTaker =
        builder_.createAllocStackInst(GenAnonymousLabelName("isReturnTaker"));

    if (isNewBE_) {
      auto* yield = builder_.createYieldFinallyInst(
          value, inputTaker, isReturnTaker, nextBB, finallyBB);
      yield->setYieldStar(true);
      yieldStarInst = yield;
    } else {
      yieldStarInst =
          builder_.createYieldStarInst(value, inputTaker, isReturnTaker);
      builder_.createCondBranchInst(builder_.createLoadStackInst(isReturnTaker),
                                    finallyBB, nextBB);
    }

    builder_.setInsertionBlock(finallyBB);
    GenFinallyBeforeControlChange(surrounding_try_, nullptr,
                                  ControlFlowChange::Break);
    builder_.createReturnInst(builder_.createLoadStackInst(inputTaker),
                              IsAsyncFunction(info_.literal()->kind()));

    builder_.setInsertionBlock(nextBB);
  } else {
    auto* inputTaker =
        builder_.createAllocStackInst(GenAnonymousLabelName("inputTaker"));
    auto* isReturnTaker =
        builder_.createAllocStackInst(GenAnonymousLabelName("isReturnTaker"));
    if (isNewBE_) {
      auto* yield = builder_.createYieldInst(value, inputTaker, isReturnTaker);
      yield->setYieldStar(true);
      yieldStarInst = yield;
    } else {
      yieldStarInst =
          builder_.createYieldStarInst(value, inputTaker, isReturnTaker);
    }
  }
  return yieldStarInst;
}

hermes::Value* FunctionIRTranslator::GenYieldStar(YieldStar* expr) {
  return GenYieldStarExpr(GenExpression(expr->expression()));
}

hermes::Value* FunctionIRTranslator::GenAwait(Await* expr) {
  auto value = GenExpression(expr->expression());
  auto* awaitInst = builder_.createAwaitInst(value);
  return awaitInst;
}

hermes::Value* FunctionIRTranslator::GenThrow(Throw* expr) {
  hermes::Value* rightHandVal = GenExpression(expr->exception());
  builder_.createThrowInst(rightHandVal);

  // Throw interferes with control flow, hence we need a new block.
  auto newBlock = builder_.createBasicBlock(curr_func_);
  builder_.setInsertionBlock(newBlock);
  return rightHandVal;
}

hermes::Value* FunctionIRTranslator::GenOptionalChain(OptionalChain* expr) {
  OptionalChainScope scope(this, builder_.getLiteralUndefined());
  scope.Phi()->addEntry(GenExpression(expr->expression()),
                        builder_.getInsertionBlock());
  return scope.Phi();
}

hermes::Value* FunctionIRTranslator::GenProperty(Property* expr) {
  AssignType property_kind = Property::GetAssignType(expr);
  hermes::Value* obj = nullptr;
  if (property_kind != NAMED_SUPER_PROPERTY &&
      property_kind != KEYED_SUPER_PROPERTY) {
    obj = GenExpression(expr->obj());
  }
  return GenPropertyLoad(obj, expr);
}
void FunctionIRTranslator::BuildGetAndCheckSuperConstructor(
    hermes::Value* this_function, hermes::Value* new_target,
    hermes::AllocStackInst* constructor_slot,
    hermes::BasicBlock* super_ctor_call_done) {
  bool omit_super_ctor = v8_flags.omit_default_ctors &&
                         IsDerivedConstructor(info_.literal()->kind());
  if (omit_super_ctor) {
    hermes::AllocStackInst* is_default_constructor =
        builder_.createAllocStackInst(
            GenAnonymousLabelName("is_default_constructor"));
    is_default_constructor->setType(hermes::Type::createBoolean());

    builder_.createFindNonDefaultConstructorOrConstructInst(
        this_function, new_target, is_default_constructor, constructor_slot);

    hermes::BasicBlock* BB = builder_.createBasicBlock(curr_func_);
    builder_.createCondBranchInst(
        builder_.createLoadStackInst(is_default_constructor),
        super_ctor_call_done, BB);
    builder_.setInsertionBlock(BB);
  } else {
    hermes::Value* constructor =
        builder_.createGetSuperConstructorInst(this_function);
    builder_.createStoreStackInst(constructor, constructor_slot);
  }

  // Check if the constructor is in fact a constructor.
  // TODO: builder()->ThrowIfNotSuperConstructor(constructor);
  return;
}

hermes::Value* FunctionIRTranslator::BuildCallSuper(Call* expr) {
  SuperCallReference* super = expr->expression()->AsSuperCallReference();

  // We compile the super call differently depending on the presence of spreads
  // and their positions.
  //
  // If there is only one spread and it is the final argument, there is a
  // special ConstructWithSpread bytecode.
  //
  // It there is a non-final spread, we rewrite something like
  //    super(1, ...x, 2)
  // to
  //    %reflect_construct(constructor, [1, ...x, 2], new_target)
  //
  // That is, we implement (non-last-arg) spreads in super calls via our
  // mechanism for spreads in array literals.
  const Call::SpreadPosition spread_position = expr->spread_position();

  // Prepare the constructor to the super call.
  hermes::Value* this_function = BuildVariableLoad(super->this_function_var());

  hermes::BasicBlock* super_ctor_call_done =
      builder_.createBasicBlock(curr_func_);

  hermes::AllocStackInst* constructor_slot =
      builder_.createAllocStackInst(GenAnonymousLabelName("constructor"));
  if (spread_position == Call::kHasNonFinalSpread) {
    // Generate the array containing all arguments.
    hermes::Value* array = GenArrayFromElements(*expr->arguments(), nullptr);
    hermes::Value* new_target = BuildVariableLoad(super->new_target_var());

    BuildGetAndCheckSuperConstructor(this_function, new_target,
                                     constructor_slot, super_ctor_call_done);

    hermes::Value* constructor = builder_.createLoadStackInst(constructor_slot);
    // Now pass that array to %reflect_construct.
    hermes::Value* instance = builder_.createCallBuiltinInst(
        hermes::BuiltinMethod::HermesBuiltin_reflectConstruct,
        {constructor, array, new_target});
    builder_.createStoreStackInst(instance, constructor_slot);
  } else {
    hermes::CallInst::ArgumentList args;
    for (auto arg : *expr->arguments()) {
      args.push_back(GenExpression(arg));
    }

    // The new target is loaded into the new_target register from the
    // {new.target} variable.
    hermes::Value* new_target = BuildVariableLoad(super->new_target_var());

    BuildGetAndCheckSuperConstructor(this_function, new_target,
                                     constructor_slot, super_ctor_call_done);
    hermes::Value* constructor = builder_.createLoadStackInst(constructor_slot);
    if (spread_position == Call::kHasFinalSpread) {
      hermes::Value* instance =
          builder_.createConstructWithSpreadInst(new_target, constructor, args);
      builder_.createStoreStackInst(instance, constructor_slot);
    } else {
      DCHECK_EQ(spread_position, Call::kNoSpread);
      // Call construct.
      // TODO(turbofan): For now we do gather feedback on super constructor
      // calls, utilizing the existing machinery to inline the actual call
      // target and the JSCreate for the implicit receiver allocation. This
      // is not an ideal solution for super constructor calls, but it gets
      // the job done for now. In the long run we might want to revisit this
      // and come up with a better way.
      hermes::Value* instance =
          builder_.createConstructInst(new_target, constructor, args);
      builder_.createStoreStackInst(instance, constructor_slot);
    }
  }

  builder_.createBranchInst(super_ctor_call_done);
  builder_.setInsertionBlock(super_ctor_call_done);

  hermes::Value* instance = builder_.createLoadStackInst(constructor_slot);
  BuildInstanceInitializationAfterSuperCall(this_function, instance);
  return instance;
}

hermes::Value* FunctionIRTranslator::GenCall(Call* expr) {
  Expression* callee_expr = expr->expression();
  Call::CallType call_type = expr->GetCallType();

  if (call_type == Call::SUPER_CALL) {
    return BuildCallSuper(expr);
  }

  const Call::SpreadPosition spread_position = expr->spread_position();

  hermes::Value* callee = nullptr;
  hermes::Value* obj = nullptr;
  bool implicit_undefined_receiver = false;
  hermes::CallInst::ArgumentList args;

  switch (call_type) {
    case Call::NAMED_PROPERTY_CALL:
    case Call::KEYED_PROPERTY_CALL:
    case Call::PRIVATE_CALL: {
      Property* property = callee_expr->AsProperty();
      obj = GenExpression(property->obj());
      callee = GenPropertyLoad(obj, property);
      break;
    }
    case Call::NAMED_OPTIONAL_CHAIN_PROPERTY_CALL:
    case Call::KEYED_OPTIONAL_CHAIN_PROPERTY_CALL: {
      OptionalChainScope scope(this, builder_.getLiteralUndefined());
      OptionalChain* chain = callee_expr->AsOptionalChain();
      Property* property = chain->expression()->AsProperty();
      obj = GenExpression(property->obj());
      scope.Phi()->addEntry(GenPropertyLoad(obj, property),
                            builder_.getInsertionBlock());
      callee = scope.Phi();
      break;
    }
    case Call::GLOBAL_CALL: {
      VariableProxy* proxy = callee_expr->AsVariableProxy();
      callee = BuildVariableLoad(proxy->var(), proxy->hole_check_mode());
      obj = builder_.getLiteralUndefined();
      if (spread_position == Call::kNoSpread) {
        implicit_undefined_receiver = true;
      }
      // TODO: funcname
      break;
    }
#if 0
    case Call::WITH_CALL: {
      Register receiver = register_allocator()->GrowRegisterList(&args);
      DCHECK(callee_expr->AsVariableProxy()->var()->IsLookupSlot());
      {
        RegisterAllocationScope inner_register_scope(this);
        Register name = register_allocator()->NewRegister();

        // Call %LoadLookupSlotForCall to get the callee and receiver.
        RegisterList result_pair = register_allocator()->NewRegisterList(2);
        Variable* variable = callee_expr->AsVariableProxy()->var();
        builder()
            ->LoadLiteral(variable->raw_name())
            .StoreAccumulatorInRegister(name)
            .CallRuntimeForPair(Runtime::kLoadLookupSlotForCall, name,
                                result_pair)
            .MoveRegister(result_pair[0], callee)
            .MoveRegister(result_pair[1], receiver);
      }
      break;
    }
#endif
    case Call::OTHER_CALL: {
      obj = builder_.getLiteralUndefined();
      if (spread_position == Call::kNoSpread) {
        implicit_undefined_receiver = true;
      }
      callee = GenExpression(callee_expr);
      break;
    }
    case Call::NAMED_SUPER_PROPERTY_CALL: {
      Property* property = callee_expr->AsProperty();
      obj = GenThisExpression(nullptr);
      callee = GenNamedSuperPropertyLoad(property, obj);
      break;
    }
    case Call::KEYED_SUPER_PROPERTY_CALL: {
      Property* property = callee_expr->AsProperty();
      obj = GenThisExpression(nullptr);
      callee = GenKeyedSuperPropertyLoad(property, obj);
      break;
    }
#if 0
    case Call::NAMED_OPTIONAL_CHAIN_PROPERTY_CALL:
    case Call::KEYED_OPTIONAL_CHAIN_PROPERTY_CALL:
    case Call::PRIVATE_OPTIONAL_CHAIN_CALL: {
      OptionalChain* chain = callee_expr->AsOptionalChain();
      Property* property = chain->expression()->AsProperty();
      BuildOptionalChain([&]() {
        VisitAndPushIntoRegisterList(property->obj(), &args);
        VisitPropertyLoad(args.last_register(), property);
      });
      builder()->StoreAccumulatorInRegister(callee);
      break;
    }
    case Call::SUPER_CALL:
      UNREACHABLE();
#endif
    // TODO
    default:
      FATAL(__FUNCTION__);
  }

  if (expr->is_optional_chain_link()) {
    DCHECK(callee != nullptr);
    hermes::BasicBlock* next = builder_.createBasicBlock(curr_func_);
    builder_.createCondBranchInst(
        builder_.createBinaryOperatorInst(
            callee, builder_.getLiteralNull(),
            hermes::BinaryOperatorInst::OpKind::EqualKind),
        top_optional_chain_null_block_, next);
    builder_.setInsertionBlock(next);
  }
  DCHECK_WITH_MSG(callee != nullptr, "callee is null");

  // TODO:(wwq) Remove isNewBE_ condition when --produce-with-ir is deprecated.
  if (isNewBE_ && expr->is_possibly_eval() && expr->arguments()->length() > 0) {
    hermes::CallInst::ArgumentList evalArgs;
    evalArgs.push_back(callee);
    Expression* first_arg = expr->arguments()->first();
    evalArgs.push_back(GenExpression(first_arg));
    evalArgs.push_back(builder_.createGetClosure());
    hermes::LiteralNumber* language_mode =
        builder_.getLiteralNumber((curr_func_->isStrictMode() ? 1 : 0));
    evalArgs.push_back(language_mode);
    // TODO:(wwq) replace the placeholder with correct eval_scope_info_index
    hermes::LiteralNumber* eval_scope_info_index =
        builder_.getLiteralNumber(expr->eval_scope_info_index());
    evalArgs.push_back(eval_scope_info_index);
    // TODO:(wwq) replace the placeholder with correct eval_position
    hermes::LiteralNumber* eval_position =
        builder_.getLiteralNumber(expr->position());
    evalArgs.push_back(eval_position);
    // total 6 args: eval_function, expr_to_eval, outer_function, language_mode,
    // eval_scope_info_index, eval_position
    callee = builder_.createCallBuiltinInst(
        hermes::BuiltinMethod::HermesBuiltin_resolvePossiblyDirectEval,
        evalArgs);
    curr_func_->setHasEval(true);
  }

  if (spread_position == Call::kHasNonFinalSpread) {
    // If we're building %reflect_apply, build the array literal and put it in
    // the 3rd argument.
    CHECK(!implicit_undefined_receiver);
    CHECK(callee && obj);
    args.push_back(callee);
    args.push_back(obj);
    args.push_back(GenArrayFromElements(*expr->arguments()));
  } else {
    // If we're not building %reflect_apply and don't need to build an array
    // literal, pop the callee and evaluate all arguments to the function call
    // and store in sequential args registers.
    for (auto arg : *expr->arguments()) {
      args.push_back(GenExpression(arg));
    }
  }

  // TODO: eval
  if (!isNewBE_ && expr->is_possibly_eval() &&
      expr->arguments()->length() > 0) {
    LOG_ERROR << "eval not fully support yet\n";
  }

  if (spread_position == Call::kHasFinalSpread) {
    CHECK(!implicit_undefined_receiver);
    return builder_.createCallWithSpreadInst(
        hermes::CallInst::kNoTextifiedCallee, callee, obj, args);
  } else if (spread_position == Call::kHasNonFinalSpread) {
    CHECK(!implicit_undefined_receiver);
    return builder_.createCallBuiltinInst(
        hermes::BuiltinMethod::HermesBuiltin_reflectApply, args);
  } else if (call_type == Call::NAMED_PROPERTY_CALL ||
             call_type == Call::KEYED_PROPERTY_CALL) {
    CHECK(!implicit_undefined_receiver);
    return builder_.createCallInst(
        hermes::CallInst::kNoTextifiedCallee, callee, obj, args,
        hermes::CallInst::CallType::CallReceiverProperty);
  } else if (implicit_undefined_receiver) {
    return builder_.createCallInst(
        hermes::CallInst::kNoTextifiedCallee, callee, obj, args,
        hermes::CallInst::CallType::CallReceiverUndefined);
  } else {
    return builder_.createCallInst(hermes::CallInst::kNoTextifiedCallee, callee,
                                   obj, args);
  }
}

hermes::Value* FunctionIRTranslator::GenCallNew(CallNew* expr) {
  hermes::Value* callee = GenExpression(expr->expression());

  const CallNew::SpreadPosition spread_position = expr->spread_position();

  if (spread_position == CallNew::kHasNonFinalSpread) {
    hermes::Value* array = GenArrayFromElements(*expr->arguments());
    return builder_.createCallBuiltinInst(
        hermes::BuiltinMethod::HermesBuiltin_reflectConstruct, {callee, array});
  }

  hermes::ConstructInst::ArgumentList args;
  for (auto* arg : *expr->arguments()) {
    args.push_back(GenExpression(arg));
  }

  if (spread_position == CallNew::kNoSpread) {
    return builder_.createConstructInst(callee, callee, args);
  } else if (spread_position == CallNew::kHasFinalSpread) {
    return builder_.createConstructWithSpreadInst(callee, callee, args);
  } else {
    FATAL(__FUNCTION__);
  }
}
void FunctionIRTranslator::BuildInstanceMemberInitialization(
    hermes::Value* constructor, hermes::Value* instance) {
  hermes::Value* prop =
      builder_.getLiteralSymbol(hermes::LiteralSymbol::ClassFieldsSymbol);
  hermes::Value* initializer =
      builder_.createLoadPropertyInst(constructor, prop);

  hermes::BasicBlock* initBB = builder_.createBasicBlock(curr_func_);
  hermes::BasicBlock* doneBB = builder_.createBasicBlock(curr_func_);
  builder_.createCondBranchInst(
      builder_.createBinaryOperatorInst(
          initializer, builder_.getLiteralUndefined(),
          getBinaryOperatorInstKind(Token::kEqStrict)),
      doneBB, initBB);

  builder_.setInsertionBlock(initBB);
  builder_.createCallInst(hermes::CallInst::kNoTextifiedCallee, initializer,
                          instance, {},
                          hermes::CallInst::CallType::CallReceiverProperty);
  builder_.createBranchInst(doneBB);

  builder_.setInsertionBlock(doneBB);
}
hermes::Value* FunctionIRTranslator::GenSuperCallForwardArgs(
    SuperCallForwardArgs* expr) {
  SuperCallReference* super = expr->expression();
  hermes::Value* this_function = BuildVariableLoad(super->this_function_var());
  hermes::Value* new_target = BuildVariableLoad(super->new_target_var());

  // TODO: support omit_super_ctor
  hermes::Value* constructor =
      builder_.createGetSuperConstructorInst(this_function);
  // TODO: ThrowIfNotSuperConstructor

  hermes::Value* instance =
      builder_.createConstructForwardAllArgsInst(new_target, constructor);
  BuildInstanceInitializationAfterSuperCall(this_function, instance);
  return instance;
}
hermes::Value* FunctionIRTranslator::GenCallRuntime(CallRuntime* expr) {
  FATAL(__FUNCTION__);
}

hermes::Value* FunctionIRTranslator::GenUnaryOperation(UnaryOperation* expr) {
  switch (expr->op()) {
    case Token::kNot:
      return GenNot(expr);
    case Token::kTypeOf:
      return GenTypeOf(expr);
    case Token::kVoid:
      (void)GenExpression(expr->expression());
      return builder_.getLiteralUndefined();
    case Token::kDelete:
      return GenDelete(expr);
    case Token::kAdd:
    case Token::kSub:
    case Token::kBitNot: {
      auto* value = GenExpression(expr->expression());
      return builder_.createUnaryOperatorInst(
          value, getUnaryOperatorInstKind(expr->op()));
    }
    default:
      FATAL(__FUNCTION__);
      break;
  }
}

hermes::Value* FunctionIRTranslator::GenCountOperation(CountOperation* expr) {
  CHECK(expr->expression()->IsValidReferenceExpression());

  // Left-hand side can only be a property, a global or a variable slot.
  Property* property = expr->expression()->AsProperty();
  AssignType assign_type = Property::GetAssignType(property);

  bool is_postfix = expr->is_postfix();

  // Evaluate LHS expression and get old value.
  hermes::Value* value = nullptr;
  hermes::Value* old_value = nullptr;
  hermes::Value* object = nullptr;
  hermes::Value* key = nullptr;
  hermes::CallInst::ArgumentList super_property_args;
  const AstRawString* name = nullptr;
  switch (assign_type) {
    case NON_PROPERTY: {
      VariableProxy* proxy = expr->expression()->AsVariableProxy();
      value = BuildVariableLoad(proxy->var(), proxy->hole_check_mode());
      break;
    }
    case NAMED_PROPERTY: {
      object = GenExpression(property->obj());
      name = property->key()->AsLiteral()->AsRawPropertyName();
      value = builder_.createLoadPropertyInst(object,
                                              getIdentifierFromAstRaw(name));
      break;
    }
    case KEYED_PROPERTY: {
      object = GenExpression(property->obj());
      // Use visit for accumulator here since we need the key in the accumulator
      // for the LoadKeyedProperty.
      key = GenExpression(property->key());
      value = builder_.createLoadPropertyInst(object, key);
      break;
    }
    case NAMED_SUPER_PROPERTY: {
      super_property_args.push_back(GenThisExpression(nullptr));
      super_property_args.push_back(BuildVariableLoad(
          property->obj()->AsSuperPropertyReference()->home_object()->var(),
          HoleCheckMode::kElided));
      super_property_args.push_back(BuildLiteralString(
          property->key()->AsLiteral()->AsRawPropertyName()));
      value = builder_.createCallBuiltinInst(
          hermes::BuiltinMethod::HermesBuiltin_PBCLoadFromSuper,
          super_property_args);
      break;
    }
    case KEYED_SUPER_PROPERTY: {
      super_property_args.push_back(GenThisExpression(nullptr));
      super_property_args.push_back(BuildVariableLoad(
          property->obj()->AsSuperPropertyReference()->home_object()->var(),
          HoleCheckMode::kElided));
      super_property_args.push_back(GenExpression(property->key()));
      value = builder_.createCallBuiltinInst(
          hermes::BuiltinMethod::HermesBuiltin_PBCLoadKeyedFromSuper,
          super_property_args);
      break;
    }
    case PRIVATE_METHOD: {
      object = GenExpression(property->obj());
      BuildPrivateBrandCheck(property, object);
      BuildInvalidPropertyAccess(MessageTemplate::kInvalidPrivateMethodWrite,
                                 property);
      return builder_.getLiteralUndefined();
    }
    case PRIVATE_GETTER_ONLY: {
      object = GenExpression(property->obj());
      BuildPrivateBrandCheck(property, object);
      BuildInvalidPropertyAccess(MessageTemplate::kInvalidPrivateSetterAccess,
                                 property);
      return builder_.getLiteralUndefined();
    }
    case PRIVATE_SETTER_ONLY: {
      object = GenExpression(property->obj());
      BuildPrivateBrandCheck(property, object);
      BuildInvalidPropertyAccess(MessageTemplate::kInvalidPrivateGetterAccess,
                                 property);
      return builder_.getLiteralUndefined();
    }
    case PRIVATE_GETTER_AND_SETTER: {
      object = GenExpression(property->obj());
      key = GenExpression(property->key());
      BuildPrivateBrandCheck(property, object);
      value = BuildPrivateGetterAccess(object, key);
      break;
    }
#if 0
    case PRIVATE_DEBUG_DYNAMIC: {
      object = GenExpression(property->obj());
      BuildPrivateDebugDynamicGet(property, object);
      break;
    }
#endif
    default: {
      FATAL(__FUNCTION__);
      break;
    }
  }

  // Save result for postfix expressions.
  if (is_postfix) {
    value = old_value = builder_.createAsNumericInst(value);
  }

  // Perform +1/-1 operation.
  value = builder_.createUnaryOperatorInst(
      value, getUnaryOperatorInstKind(expr->op()));

  // Store the value.
  switch (assign_type) {
    case NON_PROPERTY: {
      VariableProxy* proxy = expr->expression()->AsVariableProxy();
      BuildVariableAssignment(value, proxy->var(), expr->op(),
                              proxy->hole_check_mode());
      break;
    }
    case NAMED_PROPERTY: {
      builder_.createStorePropertyInst(value, object,
                                       getIdentifierFromAstRaw(name));
      break;
    }
    case KEYED_PROPERTY: {
      builder_.createStorePropertyInst(value, object, key);
      break;
    }
    case NAMED_SUPER_PROPERTY: {
      super_property_args.push_back(value);
      builder_.createCallBuiltinInst(
          hermes::BuiltinMethod::HermesBuiltin_PBCStoreToSuper,
          super_property_args);
      break;
    }
    case KEYED_SUPER_PROPERTY: {
      super_property_args.push_back(value);
      builder_.createCallBuiltinInst(
          hermes::BuiltinMethod::HermesBuiltin_PBCStoreKeyedToSuper,
          super_property_args);
      break;
    }
    case PRIVATE_SETTER_ONLY:
    case PRIVATE_GETTER_ONLY:
    case PRIVATE_METHOD: {
      UNREACHABLE();
    }
    case PRIVATE_GETTER_AND_SETTER: {
      BuildPrivateSetterAccess(object, key, value);
      break;
    }
#if 0
    case PRIVATE_DEBUG_DYNAMIC: {
      Register value = register_allocator()->NewRegister();
      builder()->StoreAccumulatorInRegister(value);
      BuildPrivateDebugDynamicSet(property, object, value);
      break;
    }
#endif
    default: {
      FATAL(__FUNCTION__);
      break;
    }
  }

  if (is_postfix) {
    value = old_value;
  }
  return value;
}

inline auto getSubExpressionFunctionPointer(Token::Value kind) {
  switch (kind) {
    case Token::kOr:
      return &FunctionIRTranslator::GenLogicalOrSubExpression;
    case Token::kAnd:
      return &FunctionIRTranslator::GenLogicalAndSubExpression;
    case Token::kNullish:
      return &FunctionIRTranslator::GenNullishSubExpression;
    default:
      FATAL(__FUNCTION__);
  }
}

hermes::Value* FunctionIRTranslator::GenBinaryOperation(
    BinaryOperation* binop) {
  switch (binop->op()) {
    case Token::kComma: {
      (void)GenExpression(binop->left());
      return GenExpression(binop->right());
    }
    case Token::kOr:
    case Token::kAnd:
    case Token::kNullish: {
      hermes::BasicBlock* exitBlock = builder_.createBasicBlock(curr_func_);
      hermes::AllocStackInst* temp =
          builder_.createAllocStackInst(GenAnonymousLabelName("logical"));
      auto subExpression = getSubExpressionFunctionPointer(binop->op());
      hermes::Value* value =
          (this->*subExpression)(binop->left(), exitBlock, temp);
      if (value != nullptr) {
        return value;
      }
      value = GenExpression(binop->right());
      builder_.createStoreStackInst(value, temp);
      builder_.createBranchInst(exitBlock);
      builder_.setInsertionBlock(exitBlock);
      return builder_.createLoadStackInst(temp);
    }

    default:
      return GenArithmeticExpression(binop);
  }
}

hermes::Value* FunctionIRTranslator::GenNaryOperation(NaryOperation* expr) {
  switch (expr->op()) {
    case Token::kComma: {
      CHECK_GT(expr->subsequent_length(), 0);
      GenExpression(expr->first());
      for (size_t i = 0; i < expr->subsequent_length() - 1; ++i) {
        (void)GenExpression(expr->subsequent(i));
      }
      return GenExpression(expr->subsequent(expr->subsequent_length() - 1));
    }
    case Token::kOr:
    case Token::kAnd:
    case Token::kNullish: {
      hermes::BasicBlock* exitBlock = builder_.createBasicBlock(curr_func_);
      hermes::AllocStackInst* temp =
          builder_.createAllocStackInst(GenAnonymousLabelName("logical"));
      auto subExpression = getSubExpressionFunctionPointer(expr->op());
      hermes::Value* value =
          (this->*subExpression)(expr->first(), exitBlock, temp);
      if (value != nullptr) {
        return value;
      }

      for (size_t i = 0; i < expr->subsequent_length() - 1; ++i) {
// TODO: Optimization by using SMI IR.
#if 0
        if (expr->subsequent(i)->IsSmiLiteral()) {
          builder()->SetExpressionPosition(expr->subsequent_op_position(i));
          builder()->BinaryOperationSmiLiteral(
              expr->op(), expr->subsequent(i)->AsLiteral()->AsSmiLiteral(),
              feedback_index(feedback_spec()->AddBinaryOpICSlot()));
        } else {
#endif
        value = (this->*subExpression)(expr->subsequent(i), exitBlock, temp);
        if (value != nullptr) {
          return value;
        }
#if 0 
        }
#endif
      }
      value = GenExpression(expr->subsequent(expr->subsequent_length() - 1));
      builder_.createStoreStackInst(value, temp);
      builder_.createBranchInst(exitBlock);
      builder_.setInsertionBlock(exitBlock);
      return builder_.createLoadStackInst(temp);
    }
    default:
      return GenNaryArithmeticExpression(expr);
  }
}

static bool IsTypeof(Expression* expr) {
  UnaryOperation* maybe_unary = expr->AsUnaryOperation();
  return maybe_unary != nullptr && maybe_unary->op() == Token::kTypeOf;
}

static bool IsCharU(const AstRawString* str) {
  return str->length() == 1 && str->FirstCharacter() == 'u';
}

static bool IsLiteralCompareTypeof(
    CompareOperation* expr, Expression** sub_expr,
    interpreter::TestTypeOfFlags::LiteralFlag* flag,
    const AstStringConstants* ast_constants, Literal** literal) {
  if (IsTypeof(expr->left()) && expr->right()->IsStringLiteral()) {
    Literal* right_lit = expr->right()->AsLiteral();
    *literal = right_lit;
    if (Token::IsEqualityOp(expr->op())) {
      // typeof(x) === 'string'
      *flag = interpreter::TestTypeOfFlags::GetFlagForLiteral(ast_constants,
                                                              right_lit);
    } else if (expr->op() == Token::kGreaterThan &&
               IsCharU(right_lit->AsRawString())) {
      // typeof(x) > 'u'
      // Minifier may convert `typeof(x) === 'undefined'` to this form,
      // since `undefined` is the only valid value that is greater than 'u'.
      // Check the test OnlyUndefinedGreaterThanU in bytecodes-unittest.cc
      *flag = interpreter::TestTypeOfFlags::LiteralFlag::kUndefined;
    } else {
      return false;
    }

    *sub_expr = expr->left()->AsUnaryOperation()->expression();
    return true;
  }

  if (IsTypeof(expr->right()) && expr->left()->IsStringLiteral()) {
    Literal* left_lit = expr->left()->AsLiteral();
    *literal = left_lit;

    if (Token::IsEqualityOp(expr->op())) {
      // 'string' === typeof(x)
      *flag = interpreter::TestTypeOfFlags::GetFlagForLiteral(ast_constants,
                                                              left_lit);
    } else if (expr->op() == Token::kLessThan &&
               IsCharU(left_lit->AsRawString())) {
      // 'u' < typeof(x)
      *flag = interpreter::TestTypeOfFlags::LiteralFlag::kUndefined;
    } else {
      return false;
    }

    *sub_expr = expr->right()->AsUnaryOperation()->expression();
    return true;
  }

  return false;
}

hermes::Value* FunctionIRTranslator::GenCompareOperation(
    CompareOperation* expr) {
  Expression* sub_expr;
  Literal* literal;
  interpreter::TestTypeOfFlags::LiteralFlag flag;
  if (IsLiteralCompareTypeof(expr, &sub_expr, &flag, ast_string_constants_,
                             &literal)) {
    // Emit a fast literal comparion for expressions of the form:
    // typeof(x) === 'string'.
    if (flag == interpreter::TestTypeOfFlags::LiteralFlag::kOther) {
      return builder_.getLiteralBool(false);
    } else {
      // Always use strict equal.
      hermes::Value* obj = GenForTypeOfValue(sub_expr);
      return builder_.createTestTypeOfInst(
          obj, interpreter::TestTypeOfFlags::Encode(flag));
    }
  } else if (expr->IsLiteralStrictCompareBoolean(&sub_expr, &literal)) {
    CHECK(expr->op() == Token::kEqStrict);
    CHECK(literal->IsBooleanLiteral());
    auto* obj = GenExpression(sub_expr);
    return builder_.createBinaryOperatorInst(
        obj, builder_.getLiteralBool(literal->AsBooleanLiteral()),
        getBinaryOperatorInstKind(Token::kEqStrict));
  } else if (expr->IsLiteralCompareUndefined(&sub_expr)) {
    // TODO: Optimization by add hermes IR.
    auto* obj = GenExpression(sub_expr);
    return builder_.createBinaryOperatorInst(
        obj, builder_.getLiteralUndefined(),
        getBinaryOperatorInstKind(expr->op()));
  } else if (expr->IsLiteralCompareNull(&sub_expr)) {
    // TODO: Optimization by add hermes IR.
    auto* obj = GenExpression(sub_expr);
    return builder_.createBinaryOperatorInst(
        obj, builder_.getLiteralNull(), getBinaryOperatorInstKind(expr->op()));
  } else {
    if (expr->op() == Token::kIn && expr->left()->IsPrivateName()) {
      Variable* var = expr->left()->AsVariableProxy()->var();
      if (IsPrivateMethodOrAccessorVariableMode(var->mode())) {
        return BuildPrivateMethodIn(var, expr->right());
      }
      // For private fields, the code below does the right thing.
    }

    auto* lhs = GenExpression(expr->left());
    auto* rhs = GenExpression(expr->right());
    return builder_.createBinaryOperatorInst(
        lhs, rhs, getBinaryOperatorInstKind(expr->op()));
  }
  // TODO:
  //   // Always returns a boolean value.
  //   execution_result()->SetResultIsBoolean();
}

hermes::Value* FunctionIRTranslator::GenSpread(Spread* expr) {
  return GenExpression(expr->expression());
}

hermes::Value* FunctionIRTranslator::GenEmptyParentheses(
    EmptyParentheses* expr) {
  FATAL(__FUNCTION__);
}

hermes::Value* FunctionIRTranslator::GenImportCallExpression(
    ImportCallExpression* expr) {
  hermes::Value* closure = builder_.createGetClosure();
  hermes::Value* specifier = GenExpression(expr->specifier());
  hermes::Value* phase =
      builder_.getLiteralNumber(static_cast<int>(expr->phase()));
  hermes::Value* options = expr->import_options()
                               ? GenExpression(expr->import_options())
                               : builder_.getLiteralUndefined();
  return builder_.createCallBuiltinInst(
      hermes::BuiltinMethod::HermesBuiltin_PBCDynamicImportCall,
      {closure, specifier, phase, options});
}

hermes::Value* FunctionIRTranslator::GenGetTemplateObject(
    GetTemplateObject* expr) {
  bool raw_and_cooked_match = true;
  hermes::GetTemplateObjectInst::RawStringInfo raw_strings;
  CHECK_EQ(expr->raw_strings()->length(), expr->cooked_strings()->length());
  {
    for (int i = 0; i < expr->raw_strings()->length(); ++i) {
      if (expr->raw_strings()->at(i) != expr->cooked_strings()->at(i)) {
        // If the AstRawStrings don't match, then neither should the allocated
        // Strings, since the AstValueFactory should have deduplicated them
        // already.
        DCHECK_IMPLIES(expr->cooked_strings()->at(i) != nullptr,
                       *expr->cooked_strings()->at(i)->string() !=
                           *expr->raw_strings()->at(i)->string());

        raw_and_cooked_match = false;
      }
      raw_strings.push_back(builder_.getLiteralString(
          getIdentifierFromAstRaw(expr->raw_strings()->at(i))));
    }
  }

  hermes::GetTemplateObjectInst::CookedStringInfo cooked_strings;
  if (!raw_and_cooked_match) {
    for (int i = 0; i < expr->cooked_strings()->length(); ++i) {
      if (expr->cooked_strings()->at(i) != nullptr) {
        cooked_strings.push_back(builder_.getLiteralString(
            getIdentifierFromAstRaw(expr->cooked_strings()->at(i))));
      } else {
        cooked_strings.push_back(builder_.getLiteralUndefined());
      }
    }
  }
  return builder_.createGetTemplateObjectInst(raw_strings, cooked_strings,
                                              raw_and_cooked_match);
}

hermes::Value* FunctionIRTranslator::GenTemplateLiteral(TemplateLiteral* expr) {
  const ZonePtrList<const AstRawString>& parts = *expr->string_parts();
  const ZonePtrList<Expression>& substitutions = *expr->substitutions();
  // Template strings with no substitutions are turned into StringLiterals.
  CHECK_GT(substitutions.length(), 0);
  CHECK_EQ(parts.length(), substitutions.length() + 1);

  // Generate string concatenation
  // TODO(hh): add feedback slot if it is used
  //   FeedbackSlot slot = feedback_spec()->AddBinaryOpICSlot();
  hermes::Value* last_part = nullptr;
  bool last_part_valid = false;
  bool has_add_string = false;

  for (int i = 0; i < substitutions.length(); ++i) {
    if (i != 0) {
      last_part_valid = true;
    }

    if (!parts[i]->IsEmpty()) {
      hermes::Value* part =
          builder_.getLiteralString(getIdentifierFromAstRaw(parts[i]));
      if (last_part_valid) {
        part = builder_.createBinaryOperatorInst(
            last_part, part, hermes::BinaryOperatorInst::OpKind::AddKind);
        has_add_string = true;
      }
      last_part = part;
      last_part_valid = true;
    }

    auto* sub = GenExpression(substitutions[i]);
    sub = builder_.createToStringInst(sub);
    if (last_part_valid) {
      sub = builder_.createBinaryOperatorInst(
          last_part, sub, hermes::BinaryOperatorInst::OpKind::AddKind);
      has_add_string = true;
    }
    last_part = sub;
    last_part_valid = false;
  }

  if (!parts.last()->IsEmpty()) {
    hermes::Value* part =
        builder_.getLiteralString(getIdentifierFromAstRaw(parts.last()));
    last_part = builder_.createBinaryOperatorInst(
        last_part, part, hermes::BinaryOperatorInst::OpKind::AddKind);
    has_add_string = true;
  }

  if (!has_add_string) {
    // Add empty string to call toString function and handle exceptions.
    last_part = builder_.createBinaryOperatorInst(
        last_part, builder_.getLiteralString(""),
        hermes::BinaryOperatorInst::OpKind::AddKind);
  }
  return last_part;
}

hermes::Value* FunctionIRTranslator::GenThisExpression(ThisExpression* expr) {
  DeclarationScope* receiver_scope = closure_scope_->GetReceiverScope();
  Variable* var = receiver_scope->receiver();
  // TODO: implement 'this' hole check elimination.
  HoleCheckMode hole_check_mode =
      IsDerivedConstructor(receiver_scope->function_kind())
          ? HoleCheckMode::kRequired
          : HoleCheckMode::kElided;
  return BuildVariableLoad(var, hole_check_mode);
}

hermes::Value* FunctionIRTranslator::GenSuperCallReference(
    SuperCallReference* expr) {
  FATAL(__FUNCTION__);
}

hermes::Value* FunctionIRTranslator::GenSuperPropertyReference(
    SuperPropertyReference* expr) {
  FATAL(__FUNCTION__);
}

hermes::CreateArgumentsInst::ArgumentType ToIRArgType(
    CreateArgumentsType type) {
  switch (type) {
    case CreateArgumentsType::kMappedArguments:
      return hermes::CreateArgumentsInst::ArgumentType::kMappedArguments;
    case CreateArgumentsType::kUnmappedArguments:
      return hermes::CreateArgumentsInst::ArgumentType::kUnmappedArguments;
    case CreateArgumentsType::kRestParameter:
      return hermes::CreateArgumentsInst::ArgumentType::kRestParameter;
  }
}

void FunctionIRTranslator::GenArgumentsObject(Variable* variable) {
  if (variable == nullptr) return;

  CHECK(variable->IsContextSlot() || variable->IsStackAllocated());

  auto type = closure_scope_->GetArgumentsType();
  hermes::CreateArgumentsInst::ArgumentType argumentType = ToIRArgType(type);
  auto argument = builder_.createCreateArgumentsInst(argumentType);
  if (variable->IsContextSlot()) {
    auto var =
        builder_.createVariable(CurrentScopeDesc(), getVarDeclKind(variable),
                                GenAnonymousLabelName("arguments"));
    builder_.createStoreFrameInst(argument, var, currentIRScope_);
    nameTable_.insertIntoScope(variable, var);
  } else if (variable->IsStackLocal()) {
    builder_.createStoreStackInst(argument, locals_[variable->index()]);
  } else {
    FATAL(__FUNCTION__);
  }
}

void FunctionIRTranslator::GenRestArgumentsArray(Variable* rest) {
  if (rest == nullptr) return;

  // Allocate and initialize a new rest parameter and assign to the {rest}
  // variable.
  hermes::Value* rest_param = builder_.createCopyRestParameterInst();
  CHECK(rest->IsContextSlot() || rest->IsStackAllocated());

  if (rest->IsContextSlot()) {
    auto var =
        builder_.createVariable(CurrentScopeDesc(), getVarDeclKind(rest),
                                getIdentifierFromAstRaw(rest->raw_name()));
    builder_.createStoreFrameInst(rest_param, var, currentIRScope_);
    nameTable_.insertIntoScope(rest, var);
  } else if (rest->IsStackLocal()) {
    builder_.createStoreStackInst(rest_param, locals_[rest->index()]);
  } else {
    FATAL(__FUNCTION__);
  }
}
void FunctionIRTranslator::GenThisFunctionVariable(Variable* variable,
                                                   hermes::Value* value) {
  if (variable == nullptr) return;
  BuildVariableInit(value, variable, HoleCheckMode::kElided);
}
void FunctionIRTranslator::GenNewTargetVariable(Variable* variable) {
  if (variable == nullptr) return;
  // TODO: Open when GenGeneratorObjectVariableInitialization is supported
  // if (IsResumableFunction(info_.literal()->kind())) return;
  if (!variable->is_used()) return;
  auto new_target = builder_.createGetNewTargetInst();
  if (variable->IsContextSlot()) {
    auto var =
        builder_.createVariable(CurrentScopeDesc(), getVarDeclKind(variable),
                                GenAnonymousLabelName("new.target"));
    builder_.createStoreFrameInst(new_target, var, currentIRScope_);
    nameTable_.insertIntoScope(variable, var);
  } else if (variable->IsStackLocal()) {
    builder_.createStoreStackInst(new_target, locals_[variable->index()]);
  } else {
    FATAL(__FUNCTION__);
  }
}
// FunctionIRTranslator::GenForAccumulatorValueOrTheHole(Expression* expr)
// {FATAL(__FUNCTION__);} hermes::Value*
// FunctionIRTranslator::GenForEffect(Expression* expr) {printf("not implement
// yet");FATAL(__FUNCTION__);} hermes::Value*
// FunctionIRTranslator::GenForRegisterValue(Expression* expr) {printf("not
// implement yet");FATAL(__FUNCTION__);} hermes::Value*
// FunctionIRTranslator::GenForNullishTest(Expression* expr,
//                                             BytecodeLabels* then_block,
//                                             BytecodeLabels* test_next_labels,
//                                             BytecodeLabels*
//                                             else_block){printf("not
//                                             implement yet");FATAL("not
//                                             implement yet");}
// hermes::Value* FunctionIRTranslator::GenInSameTestExecutionScope(Expression*
// expr) {FATAL(__FUNCTION__);} hermes::Value*
// FunctionIRTranslator::GenInScope(Statement* stmt, Scope* scope)
// {FATAL(__FUNCTION__);} hermes::Value*
// FunctionIRTranslator::GenInHoleCheckElisionScope(T* node) {printf("not
// implement yet");FATAL(__FUNCTION__);}

hermes::Value* FunctionIRTranslator::BuildStoreGlobal(Variable* target,
                                                      hermes::Value* variable) {
  return builder_.createStorePropertyInst(
      variable, builder_.getGlobalObject(),
      getIdentifierFromAstRaw(target->raw_name()));
}

hermes::Value* FunctionIRTranslator::GenArithmeticExpression(
    BinaryOperation* binop) {
  hermes::Value* lhs = GenExpression(binop->left());
  hermes::Value* rhs = GenExpression(binop->right());
  return builder_.createBinaryOperatorInst(
      lhs, rhs, getBinaryOperatorInstKind(binop->op()));
}

void FunctionIRTranslator::GenExpressionBranch(Expression* expr,
                                               hermes::BasicBlock* onTrue,
                                               hermes::BasicBlock* onFalse,
                                               hermes::BasicBlock* onNullish) {
  switch (expr->node_type()) {
    case AstNode::kBinaryOperation: {
      auto* binop = static_cast<BinaryOperation*>(expr);
      if (!isLogicalKind(binop->op())) break;
      return GenLogicalExpressionBranch(static_cast<BinaryOperation*>(expr),
                                        onTrue, onFalse, onNullish);
    }

    case AstNode::kUnaryOperation: {
      auto* e = static_cast<UnaryOperation*>(expr);
      switch (e->op()) {
        case Token::kNot:
          // Do not propagate onNullish here because !expr cannot be nullish.
          return GenExpressionBranch(e->expression(), onFalse, onTrue, nullptr);
        default:
          break;
      }

      break;
    }

#if 0
      casehermes::ESTree::NodeKind::SequenceExpression: {
        auto* e = cast<ESTree::SequenceExpressionNode>(expr);

       hermes::ESTree::NodePtr last = nullptr;
        for (auto& ex : e->_expressions) {
          if (last) GenExpression(last);
          last = &ex;
        }
        if (last) GenExpressionBranch(last, onTrue, onFalse, onNullish);
        return;
      }
#endif

    default:
      break;
  }

  hermes::Value* condVal = GenExpression(expr);
  if (onNullish) {
    hermes::Value* isNullish = builder_.createBinaryOperatorInst(
        condVal, builder_.getLiteralNull(),
        hermes::BinaryOperatorInst::OpKind::EqualKind);
    hermes::BasicBlock* notNullishBB = builder_.createBasicBlock(curr_func_);
    builder_.createCondBranchInst(isNullish, onNullish, notNullishBB);
    builder_.setInsertionBlock(notNullishBB);
  }
  builder_.createCondBranchInst(condVal, onTrue, onFalse);
}

hermes::Value* FunctionIRTranslator::GenLogicalAssignmentExpr(
    hermes::Value* lhs, const AssignmentLhsData& lhs_data,
    CompoundAssignment* expr) {
  BinaryOperation* binop = expr->binary_operation();

  // Logical assignment expressions must use short-circuiting logic.
  // BB which actually performs the assignment.
  hermes::BasicBlock* assignBB =
      builder_.createBasicBlock(builder_.getFunction());
  // BB which simply continues without performing the assignment.
  hermes::BasicBlock* continueBB =
      builder_.createBasicBlock(builder_.getFunction());

  hermes::PhiInst::ValueListType values;
  hermes::PhiInst::BasicBlockListType blocks;

  values.push_back(lhs);
  blocks.push_back(builder_.getInsertionBlock());

  if (binop->op() == Token::kNullish) {
    builder_.createCondBranchInst(
        builder_.createBinaryOperatorInst(
            lhs, builder_.getLiteralNull(),
            hermes::BinaryOperatorInst::OpKind::EqualKind),
        assignBB, continueBB);
  } else if (binop->op() == Token::kOr) {
    builder_.createCondBranchInst(lhs, continueBB, assignBB);
  } else if (binop->op() == Token::kAnd) {
    builder_.createCondBranchInst(lhs, assignBB, continueBB);
  } else {
    FATAL(__FUNCTION__);
  }

  builder_.setInsertionBlock(assignBB);
  auto* rhs = GenExpression(expr->value());
  BuildAssignment(rhs, lhs_data, expr->op());
  values.push_back(rhs);
  blocks.push_back(builder_.getInsertionBlock());
  builder_.createBranchInst(continueBB);

  builder_.setInsertionBlock(continueBB);
  // Final result is either the original value or the value assigned,
  // depending on which branch was taken.
  return builder_.createPhiInst(std::move(values), std::move(blocks));
}

void FunctionIRTranslator::GenLogicalExpressionBranch(
    BinaryOperation* logical, hermes::BasicBlock* onTrue,
    hermes::BasicBlock* onFalse, hermes::BasicBlock* onNullish) {
  auto* block = builder_.createBasicBlock(curr_func_);

  switch (logical->op()) {
    case Token::kAnd: {
      GenExpressionBranch(logical->left(), block, onFalse, onNullish);
      break;
    }
    case Token::kOr: {
      GenExpressionBranch(logical->left(), onTrue, block, onNullish);
      break;
    }
    case Token::kNullish: {
      GenExpressionBranch(logical->left(), onTrue, onFalse, block);
      break;
    }
    default: {
      FATAL(__FUNCTION__);
      break;
    }
  }

  builder_.setInsertionBlock(block);
  GenExpressionBranch(logical->right(), onTrue, onFalse, onNullish);
}

// loop {
//   iteratorRecord = iterator.next()
//   if (iteratorRecord.done) break;
//   value = iteratorRecord.value
//   array[index++] = value
// }
void FunctionIRTranslator::FillArrayWithIterator(
    IteratorRecord& iteratorRecord, hermes::Value* array,
    hermes::AllocStackInst* index) {
  auto* bodyBlock = builder_.createBasicBlock(curr_func_);
  auto* exitBlock = builder_.createBasicBlock(curr_func_);
  auto* assignValueBlock = builder_.createBasicBlock(curr_func_);

  builder_.createBranchInst(bodyBlock);
  // bodyBlock
  builder_.setInsertionBlock(bodyBlock);
  // todo(hh): IteratorAdvance produce no necessary v8 bc, change
  // IteratorAdvance or not use it?
  hermes::Value* nextValue = emitIteratorAdvance(iteratorRecord);
  hermes::Value* done = builder_.createLoadStackInst(iteratorRecord.done);
  // if(done)goto exitBlock;
  builder_.createCondBranchInst(done, exitBlock, assignValueBlock);
  builder_.setInsertionBlock(assignValueBlock);
  // array[index] = value
  auto indexVal = builder_.createLoadStackInst(index);
  builder_.createStoreOwnPropertyInst(nextValue, array, indexVal,
                                      hermes::IRBuilder::PropEnumerable::Yes);
  // index++
  auto add = builder_.createBinaryOperatorInst(
      indexVal, builder_.getLiteralNumber(1),
      hermes::BinaryOperatorInst::OpKind::AddKind);
  add->setType(hermes::Type::createNumber());
  builder_.createStoreStackInst(add, index);
  // goto bodyBlock
  builder_.createBranchInst(bodyBlock);
  // exitBlock
  builder_.setInsertionBlock(exitBlock);
}

hermes::Value* FunctionIRTranslator::GenArrayFromElements(
    const ZonePtrList<Expression>& list, ArrayLiteral* expr) {
  hermes::AllocArrayInst::ArrayValueList elements;

  // Precalculate the minimum number of elements in case we need to call
  // AllocArrayInst at some point, as well as find out whether we have a spread
  // element (which will result in the final array having variable length).
  unsigned minElements = 0;
  bool variableLength = false;
  for (auto* E : list) {
    if (E->IsSpread()) {
      variableLength = true;
      continue;
    }
    ++minElements;
  }

  // We store consecutive elements until we encounter elision,
  // or we enounter a non-literal in limited-register mode.
  // The rest of them has to be initialized through property sets.

  // If we have a variable length array, then we store the next index in
  // a stack location `nextIndex`, to be updated when we encounter spread
  // elements. Otherwise, we simply count them in `count`.
  unsigned count = 0;
  hermes::AllocStackInst* nextIndex = nullptr;
  if (variableLength) {
    // Avoid emitting the extra instructions unless we actually need to,
    // to simplify tests and because it's easy.
    nextIndex = builder_.createAllocStackInst("nextIndex");
    builder_.createStoreStackInst(builder_.getLiteralPositiveZero(), nextIndex);
  }

  bool consecutive = true;
  auto codeGenOpts = module_.getContext().getCodeGenerationSettings();
  hermes::Value* allocArrayInst = nullptr;
  ZonePtrList<Expression>::const_iterator current = list.begin();
  ZonePtrList<Expression>::const_iterator end = list.end();
  if (!list.is_empty() && (*current)->IsSpread()) {
    consecutive = false;
    hermes::Value* value = GenExpression((*current)->AsSpread()->expression());
    allocArrayInst =
        builder_.createSingleOperandInst<hermes::CreateArrayFromIterableInst>(
            value);
    if (++current != end) {
      builder_.createStoreStackInst(
          builder_.createLoadPropertyInst(
              allocArrayInst,
              BuildLiteralString(ast_string_constants_->length_string())),
          nextIndex);
    }
  } else {
    hermes::LiteralArray* literal;
    hermes::LiteralNumber* flags;
    if (expr) {
      literal = GenLiteralArray(expr);
      uint8_t createArrayLiteralFlags =
          i::interpreter::CreateArrayLiteralFlags::Encode(
              expr->builder()->IsFastCloningSupported(),
              expr->builder()->ComputeFlags());
      flags = builder_.getLiteralNumber(createArrayLiteralFlags);
    } else {
      literal = GenLiteralArray(list);
      int runtime_flags =
          portable::AggregateLiteral::kIsShallow |
          portable::AggregateLiteral::kNeedsInitialAllocationSite;
      uint8_t createArrayLiteralFlags =
          i::interpreter::CreateArrayLiteralFlags::Encode(false, runtime_flags);
      flags = builder_.getLiteralNumber(createArrayLiteralFlags);
    }

    allocArrayInst = builder_.createAllocArrayLiteralInst(literal, flags);
    while (current != end) {
      auto E = *current;
      if (!E->IsCompileTimeValue()) break;
      if (E->AsSpread()) break;
      current++;
      count++;
    }
    if (variableLength) {
      builder_.createStoreStackInst(builder_.getLiteralNumber(count),
                                    nextIndex);
    }
  }
  for (; current != end; ++current) {
    auto E = *current;
    hermes::Value* value{nullptr};
    bool isSpread = false;
    // can not use E->IsEmptyStatement(), it always return false
    if (!E->IsTheHoleLiteral()) {
      if (auto* spread = E->AsSpread()) {
        isSpread = true;
        value = GenExpression(spread->expression());
      } else {
        value = GenExpression(E);
      }
    }
    if (isSpread) {
      // Spread the SpreadElement argument into the array.
      // HermesInternal.arraySpread returns the new value of nextIndex,
      // so update nextIndex accordingly and finish this iteration of the loop.
      IteratorRecord iteratorRecord = emitIteratorInit(value, false);
      FillArrayWithIterator(iteratorRecord, allocArrayInst, nextIndex);
      continue;
    }
    // The element is not a spread element, so perform the store here.
    if (value) {
      builder_.createStoreOwnPropertyInst(
          value, allocArrayInst,
          variableLength
              ? llvh::cast<hermes::Value>(
                    builder_.createLoadStackInst(nextIndex))
              : llvh::cast<hermes::Value>(builder_.getLiteralNumber(count)),
          hermes::IRBuilder::PropEnumerable::Yes);
    }
    // Update the next index or the count depending on if it's a variable length
    // array.
    if (variableLength) {
      // We perform this update on any leading elements before the first spread
      // element as well, but the optimizer will eliminate the extra adds
      // because we know the initial value (0) and how incrementing works.
      builder_.createStoreStackInst(
          builder_.createBinaryOperatorInst(
              builder_.createLoadStackInst(nextIndex),
              builder_.getLiteralNumber(1),
              hermes::BinaryOperatorInst::OpKind::AddKind),
          nextIndex);
    } else {
      count++;
    }
  }

  if (!list.is_empty() && list.last()->IsTheHoleLiteral()) {
    // Last element is an elision, VM cannot derive the length properly.
    // We have to explicitly set it.
    hermes::Value* newLength;
    if (variableLength)
      newLength = builder_.createLoadStackInst(nextIndex);
    else
      newLength = builder_.getLiteralNumber(count);
    builder_.createStorePropertyInst(newLength, allocArrayInst,
                                     llvh::StringRef("length"));
  }
  return allocArrayInst;
}

hermes::Value* FunctionIRTranslator::GenPropertyLoad(hermes::Value* obj,
                                                     Property* property) {
  if (property->is_optional_chain_link()) {
    hermes::BasicBlock* next = builder_.createBasicBlock(curr_func_);
    builder_.createCondBranchInst(
        builder_.createBinaryOperatorInst(
            obj, builder_.getLiteralNull(),
            hermes::BinaryOperatorInst::OpKind::EqualKind),
        top_optional_chain_null_block_, next);
    builder_.setInsertionBlock(next);
  }

  AssignType property_kind = Property::GetAssignType(property);
  switch (property_kind) {
    case NON_PROPERTY:
      FATAL(__FUNCTION__);
    case NAMED_PROPERTY: {
      const AstRawString* name =
          property->key()->AsLiteral()->AsRawPropertyName();
      return builder_.createLoadPropertyInst(obj,
                                             getIdentifierFromAstRaw(name));
    }
    case KEYED_PROPERTY: {
      hermes::Value* prop = GenExpression(property->key());
      return builder_.createLoadPropertyInst(obj, prop);
    }
    case NAMED_SUPER_PROPERTY:
      return GenNamedSuperPropertyLoad(property, GenThisExpression(nullptr));

    case KEYED_SUPER_PROPERTY:
      return GenKeyedSuperPropertyLoad(property, GenThisExpression(nullptr));
    case PRIVATE_SETTER_ONLY: {
      BuildPrivateBrandCheck(property, obj);
      BuildInvalidPropertyAccess(MessageTemplate::kInvalidPrivateGetterAccess,
                                 property);
      return builder_.getLiteralUndefined();
    }
    case PRIVATE_GETTER_ONLY:
    case PRIVATE_GETTER_AND_SETTER: {
      hermes::Value* key = GenExpression(property->key());
      BuildPrivateBrandCheck(property, obj);
      return BuildPrivateGetterAccess(obj, key);
    }
    case PRIVATE_METHOD: {
      BuildPrivateBrandCheck(property, obj);
      // In the case of private methods, property->key() is the function to be
      // loaded (stored in a context slot), so load this directly.
      return GenExpression(property->key());
    }
#if 0
    case PRIVATE_DEBUG_DYNAMIC: {
      BuildPrivateDebugDynamicGet(property, obj);
      break;
    }
#endif
    default: {
      break;
    }
  }

  FATAL(__FUNCTION__);
}

hermes::Value* FunctionIRTranslator::GenNamedSuperPropertyLoad(
    Property* property, hermes::Value* receiver) {
  // TODO: support v8_flags.super_ic
  Variable* var =
      property->obj()->AsSuperPropertyReference()->home_object()->var();
  hermes::Value* obj = BuildVariableLoad(var, HoleCheckMode::kElided);
  const AstRawString* name = property->key()->AsLiteral()->AsRawPropertyName();
  hermes::Value* key = builder_.getLiteralString(getIdentifierFromAstRaw(name));
  return builder_.createCallBuiltinInst(
      hermes::BuiltinMethod::HermesBuiltin_PBCLoadFromSuper,
      {receiver, obj, key});
}

hermes::Value* FunctionIRTranslator::GenKeyedSuperPropertyLoad(
    Property* property, hermes::Value* receiver) {
  Variable* var =
      property->obj()->AsSuperPropertyReference()->home_object()->var();
  hermes::Value* obj = BuildVariableLoad(var, HoleCheckMode::kElided);
  hermes::Value* key = GenExpression(property->key());
  return builder_.createCallBuiltinInst(
      hermes::BuiltinMethod::HermesBuiltin_PBCLoadKeyedFromSuper,
      {receiver, obj, key});
}

hermes::Value* FunctionIRTranslator::GenPropertyDelete(hermes::Value* obj,
                                                       Property* property) {
  AssignType property_kind = Property::GetAssignType(property);
  switch (property_kind) {
    case NON_PROPERTY:
      UNREACHABLE();
    case NAMED_PROPERTY: {
      const AstRawString* name =
          property->key()->AsLiteral()->AsRawPropertyName();
      return builder_.createDeletePropertyInst(obj,
                                               getIdentifierFromAstRaw(name));
    }
    case KEYED_PROPERTY: {
      hermes::Value* prop = GenExpression(property->key());
      return builder_.createDeletePropertyInst(obj, prop);
    }
#if 0
    case NAMED_SUPER_PROPERTY:
      VisitNamedSuperPropertyLoad(property, hermes::Value*::invalid_value());
      break;
    case KEYED_SUPER_PROPERTY:
      VisitKeyedSuperPropertyLoad(property, hermes::Value*::invalid_value());
      break;
    case PRIVATE_SETTER_ONLY: {
      BuildPrivateBranCHECK(property, obj);
      BuildInvalidPropertyAccess(MessageTemplate::kInvalidPrivateGetterAccess,
                                 property);
      break;
    }
    case PRIVATE_GETTER_ONLY:
    case PRIVATE_GETTER_AND_SETTER: {
      hermes::Value* key = GenExpression(property->key());
      BuildPrivateBranCHECK(property, obj);
      BuildPrivateGetterAccess(obj, key);
      break;
    }
    case PRIVATE_METHOD: {
      BuildPrivateBranCHECK(property, obj);
      // In the case of private methods, property->key() is the function to be
      // loaded (stored in a context slot), so load this directly.
      VisitForAccumulatorValue(property->key());
      break;
    }
    case PRIVATE_DEBUG_DYNAMIC: {
      BuildPrivateDebugDynamicGet(property, obj);
      break;
    }
#endif
    default: {
      break;
    }
  }

  UNREACHABLE();
}

hermes::LiteralArray* FunctionIRTranslator::GenLiteralArray(
    const ZonePtrList<Expression>& list) {
  std::vector<hermes::Literal*> elements;
  int length = list.length();
  for (int i = 0; i < length; ++i) {
    Expression* element = list.at(i);
    if (!element->IsCompileTimeValue()) {
      break;
    }
    elements.emplace_back(GetBoilerplateValue(element));
  }
  return builder_.getLiteralArray(false, ElementsKind::PACKED_ELEMENTS,
                                  elements);
}

hermes::LiteralArray* FunctionIRTranslator::GenLiteralArray(
    ArrayLiteral* array) {
  ElementsKind kind = array->builder()->boilerplate_descriptor_kind();
  std::vector<hermes::Literal*> elements;
  int spreadPos = array->builder()->first_spread_index();
  int length = spreadPos >= 0 ? spreadPos : array->values()->length();
  for (int i = 0; i < length; ++i) {
    Expression* element = array->values()->at(i);
    DCHECK(!element->IsSpread());
    elements.emplace_back(GetBoilerplateValue(element));
  }
  bool isCow = false;
  if (array->builder()->is_simple() && length > 0 &&
      array->builder()->depth() ==
          i::ArrayLiteralBoilerplateBuilder::kShallow &&
      IsSmiOrObjectElementsKind(kind)) {
    isCow = true;
  }

  return builder_.getLiteralArray(isCow, kind, elements);
}

hermes::LiteralObject* FunctionIRTranslator::GenLiteralObject(
    ObjectLiteral* obj) {
  uint32_t boilerplate_properties = obj->builder()->properties_count();
  uint32_t property_length = obj->builder()->properties()->length();
  uint32_t index_keys = 0;
  bool has_seen_proto = false;
  for (int i = 0; i < property_length; i++) {
    ObjectLiteral::Property* property = obj->builder()->properties()->at(i);
    if (property->IsPrototype()) {
      has_seen_proto = true;
      continue;
    }
    if (property->is_computed_name()) continue;

    Literal* key = property->key()->AsLiteral();
    if (!key->IsPropertyName()) index_keys++;
  }

  std::vector<std::pair<hermes::Literal*, hermes::Literal*>> properties;
  uint32_t position = 0;
  for (int i = 0; i < property_length; i++) {
    ObjectLiteral::Property* prop = obj->builder()->properties()->at(i);
    if (prop->IsPrototype()) continue;

    if (position == boilerplate_properties) {
      CHECK(prop->is_computed_name());
      break;
    }
    CHECK(!prop->is_computed_name());

    hermes::Literal* key;
    if (uint32_t element_index;
        prop->key()->AsLiteral()->AsArrayIndex(&element_index)) {
      key = builder_.getLiteralNumber(element_index);
    } else {
      key = GetBoilerplateValue(prop->key());
    }

    hermes::Literal* value = GetBoilerplateValue(prop->value());
    properties.emplace_back(key, value);
    position++;
  }

  int boilerplate_description_flag = obj->builder()->EncodeLiteralType();

  hermes::LiteralObject::ObjectDescript desc(
      boilerplate_description_flag, boilerplate_properties, property_length,
      index_keys, has_seen_proto);
  return builder_.getLiteralObject(desc, properties);
}

hermes::Literal* FunctionIRTranslator::GetBoilerplateValue(Expression* expr) {
  if (expr->IsLiteral()) {
    return llvh::cast<hermes::Literal>(GenLiteral(expr->AsLiteral()));
  }
  if (expr->IsCompileTimeValue()) {
    if (expr->IsObjectLiteral()) {
      return GenLiteralObject(expr->AsObjectLiteral());
    } else if (expr->IsArrayLiteral()) {
      return GenLiteralArray(expr->AsArrayLiteral());
    }
  }
  return builder_.getLiteralUninitialized();
}

hermes::Value* FunctionIRTranslator::GenObjectExpr(ObjectLiteral* expr) {
  // TODO: support __proto__

  /// Store information about a property. Is it an accessor (getter/setter) or
  /// a value, and the actual value.
  class PropertyValue {
   public:
    /// Is this a getter/setter value.
    bool isAccessor = false;
    /// Tracks the state of generating IR for this value.
    enum { None, Placeholder, IRGenerated } state{None};
    Expression* valueNode{};
    /// Getter accessor, if this is an accessor property.
    Expression* getterNode{};
    /// Setter accessor, if this is an accessor property.
    Expression* setterNode{};

    void setValue(Expression* val) {
      isAccessor = false;
      valueNode = val;
      getterNode = setterNode = nullptr;
    }
    void setGetter(Expression* get) {
      if (!isAccessor) {
        valueNode = nullptr;
        setterNode = nullptr;
        isAccessor = true;
      }
      getterNode = get;
    }
    void setSetter(Expression* set) {
      if (!isAccessor) {
        valueNode = nullptr;
        getterNode = nullptr;
        isAccessor = true;
      }
      setterNode = set;
    }
  };

  // First accumulate all getters and setters. We walk all properties, convert
  // them to string and store the value into propMap, possibly overwriting the
  // previous value.
  // Note that computed properties are not stored in the propMap as we do not
  // know the keys at compilation time.
  llvh::DenseMap<const AstRawString*, PropertyValue> propMap;
  llvh::DenseSet<std::pair<int, const AstRawString*>> propDuplicateSet;

  /// The optional __proto__ property.
  ObjectLiteral::Property* protoProperty = nullptr;

  uint32_t numComputed = 0;
  bool hasSpread = false;
  bool hasAccessor = false;
  bool hasDuplicateProperty = false;

  for (ObjectLiteral::Property* prop : *expr->properties()) {
    if (prop->kind() == ObjectLiteral::Property::SPREAD) {
      hasSpread = true;
      continue;
    }

    if (prop->is_computed_name()) {
      // Can't store any useful information if the name is computed.
      // Just generate the code in the next loop.
      ++numComputed;
      continue;
    }

    Literal* key = prop->key()->AsLiteral();

    if (prop->IsPrototype()) {
      if (!protoProperty) {
        CHECK(prop->emit_store());
        CHECK(!prop->NeedsSetFunctionName());
        protoProperty = prop;
      } else {
        // todo(hh): ast detailed error
        FATAL(__FUNCTION__);
      }
      continue;
    }

    const AstRawString* key_str = GetLiteralString(key);
    PropertyValue* propValue = &propMap[key_str];
    int dup_kind = -1;

    if (prop->kind() == ObjectLiteral::Property::GETTER) {
      propValue->setGetter(prop->value());
      hasAccessor = true;
      dup_kind = 0;
    } else if (prop->kind() == ObjectLiteral::Property::SETTER) {
      propValue->setSetter(prop->value());
      hasAccessor = true;
      dup_kind = 1;
    } else {
      propValue->setValue(prop->value());
      dup_kind = 2;
    }

    if (!propDuplicateSet.insert(std::make_pair(dup_kind, key_str)).second) {
      hasDuplicateProperty = true;
    }
  }

  /// Attempt to determine whether we can directly use the value of the
  /// __proto__ property as a parent when creating the object for an object
  /// initializer, instead of setting it later with
  /// HermesInternal.silentSetPrototypeOf(). That is not possible to determine
  /// statically in the general case, but we can check for the simple cases:
  /// - __proto__ property is first.
  /// - the value of __proto__ is constant.
  hermes::Value* objectParent = nullptr;
#if 0
  // todo(hh)
  if (protoProperty && (&Expr->_properties.front() == protoProperty ||
                        isConstantExpr(protoProperty->_value))) {
    objectParent = GenExpression(protoProperty->_value);
  }
#endif

  // Allocate a new javascript object on the heap.
  int create_object_flags = i::interpreter::CreateObjectLiteralFlags::Encode(
      expr->builder()->ComputeFlags(),
      expr->builder()->IsFastCloningSupported());
  auto* Obj = builder_.createAllocObjectLiteralInst(
      GenLiteralObject(expr), builder_.getLiteralNumber(create_object_flags));

  // haveSeenComputedProp tracks whether we have processed a computed property.
  // Once we do, for all future properties, we can no longer generate
  // StoreNewOwnPropertyInst because the computed property could have already
  // defined any property.
  bool haveSeenComputedProp = false;

  // Initialize all properties. We check whether the value of each property
  // will be overwritten (by comparing against what we have saved in propMap).
  // In that case we still compute the value (it could have side effects), but
  // we don't store it. The exception to this are accessor functions - there
  // is no need to create them if we don't use them because creating a function
  // has no side effects.

  bool inLiteral = true;
  for (int index = 0; index < expr->properties()->length(); index++) {
    ObjectLiteral::Property* prop = expr->properties()->at(index);

    if (inLiteral) {
      if (index == expr->builder()->properties_count()) {
        inLiteral = false;
      } else if (prop->IsCompileTimeValue()) {
        // Skip value in object literal.
        continue;
      }
    }

    if (prop->kind() == ObjectLiteral::Property::SPREAD) {
      builder_.createCallBuiltinInst(
          hermes::BuiltinMethod::HermesBuiltin_copyPBCDataProperties,
          {Obj, GenExpression(prop->value())});
      haveSeenComputedProp = true;
      continue;
    }

    if (prop->is_computed_name()) {
      auto* key = GenExpression(prop->key());
      key = builder_.createSingleOperandInst<hermes::ToNameInst>(key);
      auto* value = GenExpression(prop->value());
      if (prop->kind() == ObjectLiteral::Property::GETTER) {
        builder_.createCallBuiltinInst(
            hermes::BuiltinMethod::
                HermesBuiltin_PBCDefineGetterPropertyUnchecked,
            // obj, name, getter, attrs;
            {Obj, key, value, builder_.getLiteralPositiveZero()});
      } else if (prop->kind() == ObjectLiteral::Property::SETTER) {
        builder_.createCallBuiltinInst(
            hermes::BuiltinMethod::
                HermesBuiltin_PBCDefineSetterPropertyUnchecked,
            // obj, name, setter, attrs;
            {Obj, key, value, builder_.getLiteralPositiveZero()});
      } else {
        if (prop->NeedsSetFunctionName()) {
          builder_.createCallBuiltinInst(
              hermes::BuiltinMethod::HermesBuiltin_PBCSetFunctionName,
              {value, key});
        }
        builder_.createStoreOwnPropertyInst(
            value, Obj, key, hermes::IRBuilder::PropEnumerable::Yes);
      }
      haveSeenComputedProp = true;
      continue;
    }

    Literal* key = prop->key()->AsLiteral();

    if (prop == protoProperty) {
      // This is the first definition of __proto__. If we already used it
      // as an object parent we just skip it, but otherwise we must
      // explicitly set the parent now by calling \c
      // HermesInternal.silentSetPrototypeOf().
      if (!objectParent) {
        auto* parent = GenExpression(prop->value());

        // IRBuilder::SaveRestore saveState{builder_};
        // builder_.setLocation(prop->_key->getDebugLoc());

        builder_.createCallBuiltinInst(
            hermes::BuiltinMethod::HermesBuiltin_silentSetPrototypeOf,
            {Obj, parent});
      }

      continue;
    }

    const AstRawString* keyStr = GetLiteralString(key);
    PropertyValue* propValue = &propMap[keyStr];

    // For any node that has a corresponding propValue, we need to ensure that
    // the we insert either a placeholder or the final IR before the end of this
    // iteration.
    auto checkState = llvh::make_scope_exit(
        [&] { CHECK(propValue->state != PropertyValue::None); });

    auto* Key = builder_.getLiteralString(getIdentifierFromAstRaw(keyStr));
    CHECK(llvh::isa<hermes::LiteralString>(Key));

    if (prop->kind() == ObjectLiteral::Property::GETTER ||
        prop->kind() == ObjectLiteral::Property::SETTER) {
      // If  we already generated it, skip.
      if (propValue->state == PropertyValue::IRGenerated) continue;

      if (!propValue->isAccessor) {
        propValue->state = PropertyValue::Placeholder;
        continue;
      }

      hermes::Value* getter = builder_.getLiteralNull();
      hermes::Value* setter = builder_.getLiteralNull();

      if (propValue->getterNode) {
        getter = GenExpression(propValue->getterNode);
      }

      if (propValue->setterNode) {
        setter = GenExpression(propValue->setterNode);
      }

      builder_.createCallBuiltinInst(
          hermes::BuiltinMethod::
              HermesBuiltin_PBCDefineAccessorPropertyUnchecked,
          // obj, name, getter, setter, attrs;
          {Obj, Key, getter, setter, builder_.getLiteralPositiveZero()});

      propValue->state = PropertyValue::IRGenerated;

      continue;
    }

    // Always generate the values, even if we don't need it, for the side
    // effects.
    auto value = GenExpression(prop->value());

    // Only store the value if it won't be overwritten.
    if (propMap[keyStr].valueNode == prop->value()) {
      CHECK(propValue->state != PropertyValue::IRGenerated &&
            "IR can only be generated once");
      if (haveSeenComputedProp ||
          propValue->state == PropertyValue::Placeholder) {
        builder_.createStoreOwnPropertyInst(
            value, Obj, Key, hermes::IRBuilder::PropEnumerable::Yes);
      } else {
        builder_.createStoreNewOwnPropertyInst(
            value, Obj, Key, hermes::IRBuilder::PropEnumerable::Yes);
      }
      propValue->state = PropertyValue::IRGenerated;
    } else {
      propValue->state = PropertyValue::Placeholder;
    }
  }

  // Return the newly allocated object (because this is an expression, not a
  // statement).
  return Obj;
}
void FunctionIRTranslator::GenForWhileLoops(
    IterationStatement* stmt, Statement* init, Expression* preTest,
    Expression* postTest, Statement* update, Statement* body) {
  /* In this section we generate a sequence of basic blocks that implement
    the for, while and do..while statements. Loop inversion is applied.
    For loops are syntactic-sugar for while
    loops and both have pre-test and post-test. do..while loop should only
    have post-test. They will all have the following structure:

         [ current block  ]
         [      init      ]
         [ pre test block ]
                |       \
                |        \
                |         \      ->[ exit block ]
     /-->  [ body block ]  \____/      ^
     |    [ update block ]             |
     |   [ post test block ]  --------/
     \__________/
   */

  // Create the basic blocks that make the while structure.
  hermes::BasicBlock* bodyBlock = builder_.createBasicBlock(curr_func_);
  hermes::BasicBlock* exitBlock = builder_.createBasicBlock(curr_func_);
  hermes::BasicBlock* preTestBlock = builder_.createBasicBlock(curr_func_);
  hermes::BasicBlock* postTestBlock = builder_.createBasicBlock(curr_func_);
  hermes::BasicBlock* updateBlock = builder_.createBasicBlock(curr_func_);

  // Initialize the goto labels.
  GotoLabel thisLabel(this, exitBlock, updateBlock, stmt);

  // Generate IR for the loop initialization.
  // The init field can be a variable declaration or any expression.
  // https://github.com/estree/estree/blob/master/spec.md#forstatement
  if (init) GenStatement(init);

  // Terminate the loop header section and jump to the condition block.
  builder_.createBranchInst(preTestBlock);
  builder_.setInsertionBlock(preTestBlock);

  // Branch out of the loop if the condition is false.
  if (preTest)
    GenExpressionBranch(preTest, bodyBlock, exitBlock, nullptr);
  else
    builder_.createBranchInst(bodyBlock);

  // Generate the update sequence of 'for' loops.
  builder_.setInsertionBlock(updateBlock);
  if (update) GenStatement(update);

  // After executing the content of the body, jump to the post test block.
  builder_.createBranchInst(postTestBlock);
  builder_.setInsertionBlock(postTestBlock);

  // Branch out of the loop if the condition is false.
  if (postTest)
    GenExpressionBranch(postTest, bodyBlock, exitBlock, nullptr);
  else
    builder_.createBranchInst(bodyBlock);

  // Now, generate the body of the while loop.
  // Do this last so that the test and update blocks are associated with the
  // loop statement, and not the body statement.
  builder_.setInsertionBlock(bodyBlock);
  GenStatement(body);
  builder_.createBranchInst(updateBlock);

  // Following statements are inserted to the exit block.
  builder_.setInsertionBlock(exitBlock);
}

void FunctionIRTranslator::BuildVariableInit(hermes::Value* object,
                                             Variable* target,
                                             HoleCheckMode hole_check_mode) {
  switch (target->location()) {
    case VariableLocation::LOCAL: {
      builder_.createStoreStackInst(object, locals_[target->index()]);
      break;
    }
    case VariableLocation::PARAMETER:
    case VariableLocation::CONTEXT: {
      auto var =
          builder_.createVariable(CurrentScopeDesc(), getVarDeclKind(target),
                                  getIdentifierFromAstRaw(target->raw_name()));
      builder_.createStoreFrameInst(object, var, currentIRScope_);
      nameTable_.insertIntoScope(target, var);
      break;
    }
    default: {
      FATAL(__FUNCTION__);
    }
  }
}

void FunctionIRTranslator::BuildVariableAssignment(
    hermes::Value* object, Variable* target, Token::Value op,
    HoleCheckMode hole_check_mode) {
  VariableMode mode = target->mode();
  switch (target->location()) {
    case VariableLocation::LOCAL: {
      // Assignment to const variable should be prohibited.
      if ((mode != VariableMode::kConst && mode != VariableMode::kUsing &&
           mode != VariableMode::kAwaitUsing) ||
          op == Token::kInit) {
        if (op == Token::kInit) {
          if (mode == VariableMode::kUsing) {
            FATAL(__FUNCTION__);
          } else if (mode == VariableMode::kAwaitUsing) {
            FATAL(__FUNCTION__);
          }
        }
        builder_.createStoreStackInst(object, locals_[target->index()]);
      } else if (target->throw_on_const_assignment(GetLanguageMode()) &&
                 mode == VariableMode::kConst) {
        builder_.createCallBuiltinInst(
            hermes::BuiltinMethod::HermesBuiltin_PBCThrowConstAssignError, {});
      } else if (target->throw_on_const_assignment(GetLanguageMode()) &&
                 mode == VariableMode::kUsing) {
        builder_.createCallBuiltinInst(
            hermes::BuiltinMethod::HermesBuiltin_PBCThrowUsingAssignError, {});
      }
      break;
    }
    case VariableLocation::UNALLOCATED: {
      BuildStoreGlobal(target, object);
      break;
    }
    case VariableLocation::PARAMETER:
    case VariableLocation::CONTEXT: {
      if (VariableNeedsHoleCheckInCurrentBlock(target, hole_check_mode)) {
        BuildHoleCheckForVariableAssignment(
            builder_.createLoadFrameInst(nameTable_.lookup(target),
                                         currentIRScope_),
            target);
      }
      if (mode != VariableMode::kConst || op == Token::kInit) {
        builder_.createStoreFrameInst(object, nameTable_.lookup(target),
                                      currentIRScope_);
      } else if (target->throw_on_const_assignment(GetLanguageMode())) {
        builder_.createCallBuiltinInst(
            hermes::BuiltinMethod::HermesBuiltin_PBCThrowConstAssignError, {});
      }
      break;
    }
    case VariableLocation::LOOKUP: {
      FATAL("eval not fully support yet");
      break;
    }
    default:
      FATAL(__FUNCTION__);
  }
}

void FunctionIRTranslator::BuildAssignmentWithHandler(
    Expression* lhs, const AssignmentLhsData& lhs_data, hermes::Value* value,
    SharedExceptionHandler* handler, Token::Value op) {
  if (lhs->IsVariableProxy()) {
    BuildAssignment(value, lhs_data, op);
  } else if (!lhs->IsTheHoleLiteral()) {
    emitTryWithSharedHandler(handler, [this, value, &lhs_data, op]() {
      BuildAssignment(value, lhs_data, op);
    });
  }
}

Expression* GetLhsExpression(Expression* exp) {
  if (Assignment* assign = exp->AsAssignment()) {
    return assign->target();
  } else if (Spread* spread = exp->AsSpread()) {
    return spread->expression();
  } else {
    return exp;
  }
}

Expression* GetInitExpression(Expression* exp) {
  if (Assignment* assign = exp->AsAssignment()) {
    return assign->value();
  }
  return nullptr;
}

/*
BuildArrayLiteralAssignment Pseudocode:
IteratorInit()
for(lhs in array_literal) {
  try {
    lhs_value = PrepareLhs(lhs)
  } catch {
    goto Handler;
  }

  if(lhs.isSpread()){
    value = emitIteratorRest(iteratorRecord);
    try {
      lhs_value = value
    } catch {
      goto Handler;
    }
  }

  if(!iter.done) {  // check last is done
    value = IteratorAdvance()
  }
  if(iter.done) {   // check current is done
    value = undefind
  }
  if(lhs.hasDefaultValue() && value == undefined) {
    value = lhs.getDefaultValue()
  }
  try {
    lhs_value = value
  } catch {
    goto Handler;
  }
}
IteratorFinish()

Handler:
if(!iter.done) {
  IteratorFinish()
}
Throw()
*/
void FunctionIRTranslator::BuildArrayLiteralAssignment(
    hermes::Value* object, Token::Value op, ArrayLiteral* array_literal) {
  const ZonePtrList<Expression>* values = array_literal->values();

  // if length is 0, done will be used by IteratorFinish; if first value is
  // spread, done will be used by IteratorRest; otherwise done will be produced
  // by IteratorAdvance
  const IteratorRecord iteratorRecord = emitIteratorInit(
      object, values->length() == 0 || values->at(0)->IsSpread());
  SharedExceptionHandler handler{};
  handler.exc = builder_.createAllocStackInst(GenAnonymousLabelName("exc"));
  // All exception handlers branch to this block.
  handler.exceptionBlock = builder_.createBasicBlock(builder_.getFunction());

  for (int i = 0; i < values->length(); ++i) {
    // PrepareLhs
    Expression* lhs = GetLhsExpression(values->at(i));
    AssignmentLhsData lhs_data;

    if (lhs->IsVariableProxy()) {
      lhs_data = PrepareAssignmentLhs(lhs);
    } else {
      emitTryWithSharedHandler(&handler, [this, &lhs_data, lhs]() {
        lhs_data = PrepareAssignmentLhs(lhs);
      });
    }

    // Assign rest values to spread array.
    if (values->at(i)->IsSpread()) {
      CHECK(i == values->length() - 1);
      hermes::Value* value = emitIteratorRest(iteratorRecord);
      BuildAssignmentWithHandler(lhs, lhs_data, value, &handler, op);
      break;
    }

    // Check iterator has done before IteratorAdvance
    hermes::BasicBlock* nextBlock = builder_.createBasicBlock(curr_func_);
    hermes::BasicBlock* doneBlock = builder_.createBasicBlock(curr_func_);
    if (i != 0) {
      hermes::Value* done = builder_.createLoadStackInst(iteratorRecord.done);
      builder_.createCondBranchInst(done, doneBlock, nextBlock);
    } else {
      builder_.createBranchInst(nextBlock);
    }

    // Get next value form iterator
    builder_.setInsertionBlock(nextBlock);
    hermes::Value* value = emitIteratorAdvance(iteratorRecord);
    builder_.createStoreStackInst(value, iteratorRecord.value);
    builder_.createBranchInst(doneBlock);

    // Check iterator has done after IteratorAdvance
    builder_.setInsertionBlock(doneBlock);
    hermes::BasicBlock* isDoneBlock = builder_.createBasicBlock(curr_func_);
    hermes::BasicBlock* notDoneBlock = builder_.createBasicBlock(curr_func_);
    hermes::Value* done = builder_.createLoadStackInst(iteratorRecord.done);
    builder_.createCondBranchInst(done, isDoneBlock, notDoneBlock);

    // Set undefined when iterator is done
    builder_.setInsertionBlock(isDoneBlock);
    builder_.createStoreStackInst(builder_.getLiteralUndefined(),
                                  iteratorRecord.value);
    builder_.createBranchInst(notDoneBlock);

    // Set default value
    builder_.setInsertionBlock(notDoneBlock);
    Expression* init = GetInitExpression(values->at(i));
    value = builder_.createLoadStackInst(iteratorRecord.value);
    value = emitOptionalInitialization(value, init);

    // Assign values to lhs with trap handler
    BuildAssignmentWithHandler(lhs, lhs_data, value, &handler, op);
  }

  if (handler.emittedTry) {
    hermes::IRBuilder::SaveRestore saveRestore{builder_};
    builder_.setInsertionBlock(handler.exceptionBlock);

    auto* notDoneBlock = builder_.createBasicBlock(builder_.getFunction());
    auto* doneBlock = builder_.createBasicBlock(builder_.getFunction());

    builder_.createCondBranchInst(
        builder_.createLoadStackInst(iteratorRecord.done), doneBlock,
        notDoneBlock);

    builder_.setInsertionBlock(notDoneBlock);
    emitIteratorFinish(iteratorRecord, true);
    builder_.createBranchInst(doneBlock);

    builder_.setInsertionBlock(doneBlock);
    builder_.createThrowInst(builder_.createLoadStackInst(handler.exc));
  } else {
    // If we didn't use the exception block, we need to delete it, otherwise
    // it fails IR validation even though it will be never executed.
    handler.exceptionBlock->eraseFromParent();

    // Delete the not needed exception stack allocation. It would be optimized
    // out later, but it is nice to produce cleaner non-optimized IR, if it is
    // easy to do so.
    assert(!handler.exc->hasUsers() &&
           "should not have any users if no try/catch was emitted");
    handler.exc->eraseFromParent();
  }

  if (values->length() == 0) {
    hermes::BasicBlock* doneBlock = builder_.createBasicBlock(curr_func_);
    emitIteratorFinish(iteratorRecord, false);
    builder_.createBranchInst(doneBlock);

    builder_.setInsertionBlock(doneBlock);
    return;
  }

  if (!values->last()->IsSpread()) {
    hermes::BasicBlock* isDoneBlock = builder_.createBasicBlock(curr_func_);
    hermes::BasicBlock* notDoneBlock = builder_.createBasicBlock(curr_func_);
    hermes::Value* done = builder_.createLoadStackInst(iteratorRecord.done);
    builder_.createCondBranchInst(done, isDoneBlock, notDoneBlock);

    builder_.setInsertionBlock(notDoneBlock);
    emitIteratorFinish(iteratorRecord, false);
    builder_.createBranchInst(isDoneBlock);

    builder_.setInsertionBlock(isDoneBlock);
  }
}

/*
BuildObjectLiteralAssignment Pseudocode:

if (object_literal == {} || object_literal == {...}) {
  if(rhs == null || rhs == undefined) {
    call HermesBuiltin_throwTypeError();
    return undefined;
  }
}

for (property in object_literal) {
  if (property is spread) {
    emitRestProperty();
    break;
  }
  key = property.key
  lhs = PrepareLhs();
  value = PrepareRhs(rhs[key])
  value = GetDefaultValue();
  lhs_value = value;
}

*/

void FunctionIRTranslator::BuildObjectLiteralAssignment(
    hermes::Value* object, Token::Value op, ObjectLiteral* objectLiteral) {
  ZoneList<v8::internal::ObjectLiteral::Property*>* properties =
      objectLiteral->properties();
  if (properties->length() == 0 ||
      properties->first()->kind() == ObjectLiteral::Property::SPREAD) {
    // ES10.0 13.3.3.5
    // 1. Perform ? RequireObjectCoercible(value).

    // The extremely unlikely case that the user is attempting to destructure
    // into {} or {...rest}. Any other object destructuring will fail upon
    // attempting to retrieve a real property from `source`.
    // We must check that the source can be destructured,
    // and the only time this will throw is if source is undefined or null.
    auto* throwBB = builder_.createBasicBlock(builder_.getFunction());
    auto* doneBB = builder_.createBasicBlock(builder_.getFunction());

    // Use == instead of === to account for both undefined and null.
    auto* isDone = builder_.createBinaryOperatorInst(
        object, builder_.getLiteralNull(),
        hermes::BinaryOperatorInst::OpKind::EqualKind);
    builder_.createCondBranchInst(isDone, throwBB, doneBB);
    {
      builder_.setInsertionBlock(throwBB);
      // TODO: replace HermesBuiltin_throwTypeError
      builder_.createCallBuiltinInst(
          hermes::BuiltinMethod::HermesBuiltin_PBCThrowTypeError, {object});
      // throwTypeError will always throw.
      // This return is here to ensure well-formed IR, and will not run.
      builder_.createReturnInst(builder_.getLiteralUndefined());
    }
    builder_.setInsertionBlock(doneBB);
  }

  // Keep track of which keys have been destructured.
  llvh::SmallVector<hermes::Value*, 4> excludedItems{};
  for (size_t i = 0; i < properties->length(); ++i) {
    ObjectLiteral::Property* property = properties->at(i);

    // Assign rest property to spread object.
    if (property->kind() == ObjectLiteral::Property::SPREAD) {
      CHECK(i == properties->length() - 1);
      emitRestProperty(property->value(), excludedItems, object, op);
      break;
    }

    hermes::Value* key = GenExpression(property->key());
    excludedItems.push_back(key);

    // PrepareLhs
    Expression* lhs = GetLhsExpression(property->value());
    AssignmentLhsData lhs_data = PrepareAssignmentLhs(lhs);

    // PrepareRhs
    auto* loadedValue = builder_.createLoadPropertyInst(object, key);

    // Assgin value to lhs with default value
    Expression* init = GetInitExpression(property->value());
    BuildAssignment(emitOptionalInitialization(loadedValue, init), lhs_data,
                    op);
  }
}

void FunctionIRTranslator::BuildAssignment(hermes::Value* object,
                                           const AssignmentLhsData& lhs_data,
                                           Token::Value op) {
  // Assign the value to the LHS.
  switch (lhs_data.assign_type()) {
    case NON_PROPERTY: {
      if (ObjectLiteral* pattern_as_object =
              lhs_data.expr()->AsObjectLiteral()) {
        BuildObjectLiteralAssignment(object, op, pattern_as_object);
      } else if (ArrayLiteral* pattern_as_array =
                     lhs_data.expr()->AsArrayLiteral()) {
        BuildArrayLiteralAssignment(object, op, pattern_as_array);
      } else {
        CHECK(lhs_data.expr()->IsVariableProxy());
        VariableProxy* proxy = lhs_data.expr()->AsVariableProxy();
        BuildVariableAssignment(object, proxy->var(), op,
                                proxy->hole_check_mode());
      }
      break;
    }
    case NAMED_PROPERTY:
    case KEYED_PROPERTY: {
      builder_.createStorePropertyInst(object, lhs_data.object(),
                                       lhs_data.key());
      break;
    }
    case NAMED_SUPER_PROPERTY: {
      hermes::CallInst::ArgumentList args = lhs_data.super_property_args();
      args.push_back(object);
      builder_.createCallBuiltinInst(
          hermes::BuiltinMethod::HermesBuiltin_PBCStoreToSuper, args);
      break;
    }
    case KEYED_SUPER_PROPERTY: {
      hermes::CallInst::ArgumentList args = lhs_data.super_property_args();
      args.push_back(object);
      builder_.createCallBuiltinInst(
          hermes::BuiltinMethod::HermesBuiltin_PBCStoreKeyedToSuper, args);
      break;
    }
    case PRIVATE_METHOD: {
      Property* property = lhs_data.expr()->AsProperty();
      BuildPrivateBrandCheck(property, lhs_data.object());
      BuildInvalidPropertyAccess(MessageTemplate::kInvalidPrivateMethodWrite,
                                 lhs_data.expr()->AsProperty());
      break;
    }
    case PRIVATE_GETTER_ONLY: {
      Property* property = lhs_data.expr()->AsProperty();
      BuildPrivateBrandCheck(property, lhs_data.object());
      BuildInvalidPropertyAccess(MessageTemplate::kInvalidPrivateSetterAccess,
                                 lhs_data.expr()->AsProperty());
      break;
    }
    case PRIVATE_SETTER_ONLY:
    case PRIVATE_GETTER_AND_SETTER: {
      Property* property = lhs_data.expr()->AsProperty();
      BuildPrivateBrandCheck(property, lhs_data.object());
      BuildPrivateSetterAccess(lhs_data.object(), lhs_data.key(), object);
      break;
    }
    case PRIVATE_DEBUG_DYNAMIC: {
      FATAL(__FUNCTION__);
      break;
    }
    default: {
      FATAL(__FUNCTION__);
      break;
    }
  }
}

hermes::Value* FunctionIRTranslator::GenNot(UnaryOperation* expr) {
  // TODO: Optimization by execution_result.
  // TODO: Optimization by supporting ToBoolean IR in hermes.
#if 0
  UnaryOperation* unary_op = expr->expression()->AsUnaryOperation();
  if (unary_op && unary_op->op() == Token::kNot) {
    builder_.createUnaryOperatorInst(GenExpression())
    // Shortcut repeated nots, to capture the `!!foo` pattern for converting
    // expressions to booleans.
    TypeHint type_hint = VisitForAccumulatorValue(unary_op->expression());
    builder()->ToBoolean(ToBooleanModeFromTypeHint(type_hint));
  } else {
#endif
  return builder_.createUnaryOperatorInst(
      GenExpression(expr->expression()), getUnaryOperatorInstKind(Token::kNot));
#if 0
  }
#endif
}

hermes::Value* FunctionIRTranslator::GenDelete(UnaryOperation* unary) {
  Expression* expr = unary->expression();
  if (expr->IsProperty()) {
    // Delete of an object property is allowed both in sloppy
    // and strict modes.
    Property* property = expr->AsProperty();
    CHECK(!property->IsPrivateReference());
    if (property->IsSuperAccess()) {
      // Delete of super access is not allowed.
      GenExpression(property->key());
      builder_.createCallBuiltinInst(
          hermes::BuiltinMethod::HermesBuiltin_PBCThrowUnsupportedSuperError,
          {});
      return builder_.getLiteralBool(false);
    } else {
      hermes::Value* object = GenExpression(property->obj());
      hermes::Value* key = GenExpression(property->key());
      return builder_.createDeletePropertyInst(object, key);
    }
  } else if (expr->IsOptionalChain()) {
    Expression* expr_inner = expr->AsOptionalChain()->expression();
    if (expr_inner->IsProperty()) {
      Property* property = expr_inner->AsProperty();
      DCHECK(!property->IsPrivateReference());
      OptionalChainScope scope(this, builder_.getLiteralBool(true));
      hermes::Value* obj = GenExpression(property->obj());
      if (property->is_optional_chain_link()) {
        hermes::BasicBlock* next = builder_.createBasicBlock(curr_func_);
        builder_.createCondBranchInst(
            builder_.createBinaryOperatorInst(
                obj, builder_.getLiteralNull(),
                hermes::BinaryOperatorInst::OpKind::EqualKind),
            top_optional_chain_null_block_, next);
        builder_.setInsertionBlock(next);
      }
      scope.Phi()->addEntry(GenPropertyDelete(obj, property),
                            builder_.getInsertionBlock());
      return scope.Phi();
    } else {
      return builder_.getLiteralBool(true);
    }
  } else if (expr->IsVariableProxy() &&
             !expr->AsVariableProxy()->is_new_target()) {
    // Delete of an unqualified identifier is allowed in sloppy mode but is
    // not allowed in strict mode.
    // CHECK(is_sloppy(language_mode()));
    Variable* variable = expr->AsVariableProxy()->var();
    switch (variable->location()) {
      case VariableLocation::PARAMETER:
      case VariableLocation::LOCAL:
      case VariableLocation::CONTEXT:
      case VariableLocation::REPL_GLOBAL: {
        // Deleting local var/let/const, context variables, and arguments
        // does not have any effect.
        return builder_.getLiteralBool(false);
      }
      case VariableLocation::UNALLOCATED:
      // TODO(adamk): Falling through to the runtime results in correct
      // behavior, but does unnecessary context-walking (since scope
      // analysis has already proven that the variable doesn't exist in
      // any non-global scope). Consider adding a DeleteGlobal bytecode
      // that knows how to deal with ScriptContexts as well as global
      // object properties.
      case VariableLocation::LOOKUP: {
        LOG_ERROR << "eval not fully support yet\n";
        return builder_.createCallBuiltinInst(
            hermes::BuiltinMethod::HermesBuiltin_deleteLookupSlot,
            {BuildLiteralString(variable->raw_name())});
      }
#if 0
      case VariableLocation::MODULE:
        // Modules are always in strict mode and unqualified identifers are not
        // allowed in strict mode.
        FATAL(__FUNCTION__);
#endif
      default: {
        FATAL(__FUNCTION__);
        break;
      }
    }
  } else {
    // Delete of an unresolvable reference, new.target, and this returns true.
    GenExpression(expr);
    return builder_.getLiteralBool(true);
  }
}

hermes::Value* FunctionIRTranslator::GenNaryArithmeticExpression(
    NaryOperation* expr) {
// TODO:optimization, type_hint
#if 0
  TypeHint type_hint = VisitForAccumulatorValue(expr->first());
#endif
  hermes::Value* lhs = GenExpression(expr->first());
  for (size_t i = 0; i < expr->subsequent_length(); ++i) {
// tODO:Optimization by using SMI IR.
#if 0
    if (expr->subsequent(i)->IsSmiLiteral()) {
      builder()->SetExpressionPosition(expr->subsequent_op_position(i));
      builder()->BinaryOperationSmiLiteral(
          expr->op(), expr->subsequent(i)->AsLiteral()->AsSmiLiteral(),
          feedback_index(feedback_spec()->AddBinaryOpICSlot()));
    } else {
#endif
    hermes::Value* rhs = GenExpression(expr->subsequent(i));
    lhs = builder_.createBinaryOperatorInst(
        lhs, rhs, getBinaryOperatorInstKind(expr->op()));
#if 0
    }
#endif
  }
  return lhs;
}

hermes::Value* FunctionIRTranslator::GenForTypeOfValue(Expression* expr) {
  if (expr->IsVariableProxy()) {
    // Typeof does not throw a reference error on global variables, hence we
    // perform a non-contextual load in case the operand is a variable proxy.
    VariableProxy* proxy = expr->AsVariableProxy();
    // TODO:typeof_mode
    return BuildVariableLoad(proxy->var(), proxy->hole_check_mode(),
                             TypeofMode::kInside);
  } else {
    return GenExpression(expr);
  }
}

hermes::Value* FunctionIRTranslator::GenTypeOf(UnaryOperation* expr) {
  auto* value = GenForTypeOfValue(expr->expression());
  return builder_.createUnaryOperatorInst(
      value, getUnaryOperatorInstKind(Token::kTypeOf));
}

hermes::Instruction* FunctionIRTranslator::emitStore(hermes::Value* storedValue,
                                                     hermes::Value* ptr,
                                                     bool declInit) {
  if (auto* var = llvh::dyn_cast<hermes::Variable>(ptr)) {
    if (!declInit && var->getObeysTDZ()) {
      // Must verify whether the variable is initialized.
      builder_.createThrowIfEmptyInst(
          builder_.createLoadFrameInst(var, currentIRScope_));
    }
    return builder_.createStoreFrameInst(storedValue, var, currentIRScope_);
  } else if (auto* globalProp =
                 llvh::dyn_cast<hermes::GlobalObjectProperty>(ptr)) {
    if (globalProp->isDeclared() || !curr_func_->isStrictMode())
      return builder_.createStorePropertyInst(
          storedValue, builder_.getGlobalObject(), globalProp->getName());
    else
      return builder_.createTryStoreGlobalPropertyInst(storedValue, globalProp);
  } else {
    FATAL(__FUNCTION__);
  }
}

AssignmentLhsData FunctionIRTranslator::PrepareAssignmentLhs(Expression* lhs) {
  // Left-hand side can only be a property, a global or a variable slot.
  Property* property = lhs->AsProperty();
  AssignType assign_type = Property::GetAssignType(property);

  // Evaluate LHS expression.
  switch (assign_type) {
    case NON_PROPERTY:
      return AssignmentLhsData::NonProperty(lhs);
    case NAMED_PROPERTY:
      // TODO: Same as KEYED_PROPERTY currently.
#if 0
    {
      hermes::Value* object = GenExpression(property->obj());
      const AstRawString* name =
          property->key()->AsLiteral()->AsRawPropertyName();
      return AssignmentLhsData::NamedProperty(property->obj(), object, name);
    }
#endif
    case KEYED_PROPERTY: {
      hermes::Value* object = GenExpression(property->obj());
      hermes::Value* key = GenExpression(property->key());
      return AssignmentLhsData::KeyedProperty(object, key);
    }
    case PRIVATE_METHOD:
    case PRIVATE_GETTER_ONLY:
    case PRIVATE_SETTER_ONLY:
    case PRIVATE_GETTER_AND_SETTER: {
      CHECK(!property->IsSuperAccess());
      hermes::Value* object = GenExpression(property->obj());
      hermes::Value* key = GenExpression(property->key());
      return AssignmentLhsData::PrivateMethodOrAccessor(assign_type, property,
                                                        object, key);
    }
    case PRIVATE_DEBUG_DYNAMIC: {
      hermes::Value* object = GenExpression(property->obj());
      // Do not visit the key here, instead we will look them up at run time.
      return AssignmentLhsData::PrivateDebugEvaluate(assign_type, property,
                                                     object);
    }
    case NAMED_SUPER_PROPERTY: {
      hermes::CallInst::ArgumentList super_property_args;
      super_property_args.push_back(GenThisExpression(nullptr));
      super_property_args.push_back(BuildVariableLoad(
          property->obj()->AsSuperPropertyReference()->home_object()->var(),
          HoleCheckMode::kElided));
      super_property_args.push_back(BuildLiteralString(
          property->key()->AsLiteral()->AsRawPropertyName()));
      return AssignmentLhsData::NamedSuperProperty(super_property_args);
    }
    case KEYED_SUPER_PROPERTY: {
      hermes::CallInst::ArgumentList super_property_args;
      super_property_args.push_back(GenThisExpression(nullptr));
      super_property_args.push_back(BuildVariableLoad(
          property->obj()->AsSuperPropertyReference()->home_object()->var(),
          HoleCheckMode::kElided));
      super_property_args.push_back(GenExpression(property->key()));
      return AssignmentLhsData::KeyedSuperProperty(super_property_args);
    }
  }
  FATAL(__FUNCTION__);
}

hermes::Value* FunctionIRTranslator::emitOptionalInitialization(
    hermes::Value* value, Expression* init) {
  if (init == nullptr) return value;

  auto* currentBlock = builder_.getInsertionBlock();
  auto* defaultBlock = builder_.createBasicBlock(curr_func_);
  auto* storeBlock = builder_.createBasicBlock(curr_func_);
  hermes::Value* defaultValue = nullptr;

  hermes::Value* notUndefined = builder_.createBinaryOperatorInst(
      value, builder_.getLiteralUndefined(),
      hermes::BinaryOperatorInst::OpKind::StrictlyNotEqualKind);
  builder_.createCondBranchInst(notUndefined, storeBlock, defaultBlock);
  {
    builder_.setInsertionBlock(defaultBlock);
    defaultValue = GenExpression(init);
    builder_.createBranchInst(storeBlock);
    defaultBlock = builder_.getInsertionBlock();
  }

  builder_.setInsertionBlock(storeBlock);
  return builder_.createPhiInst({value, defaultValue},
                                {currentBlock, defaultBlock});
}

void FunctionIRTranslator::emitRestProperty(
    Expression* rest,
    const llvh::SmallVectorImpl<hermes::Value*>& excludedItems,
    hermes::Value* source, Token::Value op) {
  Expression* lhs = GetLhsExpression(rest);
  AssignmentLhsData lhs_data = PrepareAssignmentLhs(lhs);

  hermes::CallInst::ArgumentList args;
  // The first argument is the source object.
  args.push_back(source);
  // Add the excluded items as the arguments.
  for (hermes::Value* key : excludedItems) {
    args.push_back(key);
  }
  auto* restValue = builder_.createCallBuiltinInst(
      hermes::BuiltinMethod::
          HermesBuiltin_CopyDataPropertiesWithExcludedPropertiesOnStack,
      args);

  BuildAssignment(restValue, lhs_data, op);
}

template <typename EB, typename EF, typename EH>
hermes::BasicBlock* FunctionIRTranslator::emitTryCatchScaffolding(
    hermes::BasicBlock* nextBlock, EB emitBody, EF emitNormalCleanup,
    EH emitHandler) {
  auto* tryBodyBlock = builder_.createBasicBlock(curr_func_);
  auto* catchBlock = builder_.createBasicBlock(curr_func_);
  if (!nextBlock) nextBlock = builder_.createBasicBlock(curr_func_);

  hermes::AllocStackInst* slot =
      builder_.createAllocStackInst(GenAnonymousLabelName("context"));
  hermes::GetRuntimeRegInst* context = builder_.createGetContext();
  builder_.createStoreStackInst(context, slot);
  // Start with a TryStartInst, and transition to try body.
  hermes::TryStartInst* inst =
      builder_.createTryStartInst(tryBodyBlock, catchBlock, slot);
  builder_.setInsertionBlock(tryBodyBlock);

  // Generate IR for the body of Try
  emitBody();

  // Emit TryEnd in a new block.
  auto* tryEndBlock = builder_.createBasicBlock(curr_func_);
  builder_.createBranchInst(tryEndBlock);
  builder_.setInsertionBlock(tryEndBlock);
  builder_.createTryEndInst();

  emitNormalCleanup();

  builder_.createBranchInst(nextBlock);

  // Generate the catch/finally block.
  builder_.setInsertionBlock(catchBlock);

  emitHandler(nextBlock, inst);

  return nextBlock;
}

template <typename EB>
void FunctionIRTranslator::emitTryWithSharedHandler(
    SharedExceptionHandler* handler, EB emitBody) {
  emitTryCatchScaffolding(
      nullptr,
      // emitBody.
      emitBody,
      // emitNormalCleanup.
      []() {},
      // emitHandler.
      [this, handler](hermes::BasicBlock* nextBlock,
                      hermes::TryStartInst* inst) {
        builder_.createStoreStackInst(
            builder_.createCatchInst(inst->getContext()), handler->exc);
        builder_.createBranchInst(handler->exceptionBlock);
        builder_.setInsertionBlock(nextBlock);
      });
  handler->emittedTry = true;
}

IteratorRecord FunctionIRTranslator::emitIteratorInit(hermes::Value* obj,
                                                      bool needInitDoneFalse,
                                                      bool isAsync) {
  auto* sourceOrNext =
      builder_.createAllocStackInst(GenAnonymousLabelName("sourceOrNext"));
  builder_.createStoreStackInst(obj, sourceOrNext);
  auto* iterStorage =
      builder_.createAllocStackInst(GenAnonymousLabelName("iter"));
  auto* iter = builder_.createIteratorInitInst(sourceOrNext, isAsync);
  builder_.createStoreStackInst(iter, iterStorage);
  hermes::AllocStackInst* value =
      builder_.createAllocStackInst(GenAnonymousLabelName("value"));
  hermes::AllocStackInst* done =
      builder_.createAllocStackInst(GenAnonymousLabelName("done"));
  if (needInitDoneFalse) {
    builder_.createStoreStackInst(builder_.getLiteralBool(false), done);
  }
  return IteratorRecord{iterStorage, sourceOrNext, value, done};
}

hermes::Value* FunctionIRTranslator::GenLogicalOrSubExpression(
    Expression* expr, hermes::BasicBlock* exitBlock,
    hermes::AllocStackInst* temp) {
  if (expr->ToBooleanIsTrue()) {
    hermes::Value* value = GenExpression(expr);
    builder_.createBranchInst(exitBlock);
    builder_.setInsertionBlock(exitBlock);
    return value;
  } else if (!expr->ToBooleanIsFalse()) {
    hermes::Value* value = GenExpression(expr);
    builder_.createStoreStackInst(value, temp);
    hermes::BasicBlock* onFalse = builder_.createBasicBlock(curr_func_);
    builder_.createCondBranchInst(value, exitBlock, onFalse);
    builder_.setInsertionBlock(onFalse);
  }

  return nullptr;
}

hermes::Value* FunctionIRTranslator::GenLogicalAndSubExpression(
    Expression* expr, hermes::BasicBlock* exitBlock,
    hermes::AllocStackInst* temp) {
  if (expr->ToBooleanIsFalse()) {
    hermes::Value* value = GenExpression(expr);
    builder_.createBranchInst(exitBlock);
    builder_.setInsertionBlock(exitBlock);
    return value;
  } else if (!expr->ToBooleanIsTrue()) {
    hermes::Value* value = GenExpression(expr);
    builder_.createStoreStackInst(value, temp);
    hermes::BasicBlock* onTrue = builder_.createBasicBlock(curr_func_);
    builder_.createCondBranchInst(value, onTrue, exitBlock);
    builder_.setInsertionBlock(onTrue);
  }

  return nullptr;
}

void FunctionIRTranslator::GenerateAsyncFunctionBody() {
  auto* nextBlock = emitTryCatchScaffolding(
      nullptr,
      // emitBody.
      [this]() {
        SurroundingTry thisTry(this, nullptr);
        GenStatements(info_.literal()->body());
      },
      // emitNormalCleanup.
      []() {
        // do nothing
      },
      // emitHandler.
      [this](hermes::BasicBlock* nextBlock, hermes::TryStartInst* inst) {
        auto* catchInst = builder_.createCatchInst(inst->getContext());
        Variable* generator_object_var = closure_scope_->generator_object_var();
        hermes::Value* generator_object =
            BuildVariableLoad(generator_object_var, HoleCheckMode::kElided);
        hermes::Value* reject = builder_.createCallBuiltinInst(
            hermes::BuiltinMethod::HermesBuiltin_PBCAsyncFunctionReject,
            {generator_object, catchInst});
        builder_.createReturnInst(reject);
      });
  builder_.setInsertionBlock(nextBlock);
  builder_.createReturnInst(builder_.getLiteralUndefined(), true);
}

void FunctionIRTranslator::GenerateAsyncGeneratorFunctionBody() {
  int start = 0;
  ZonePtrList<Statement>* statements = info_.literal()->body();
  Statement* stmt = statements->at(0);
  if (stmt->IsBlock()) {
    Block* block = static_cast<Block*>(stmt);
    if (block->is_initialization_block_for_parameters()) {
      GenStatements(block->statements());
      start = 1;
    }
  }

  std::function<void(void)> try_body = [this, &start]() {
    auto* nextBlock = emitTryCatchScaffolding(
        nullptr,
        // emitBody.
        [this, &start]() {
          SurroundingTry thisTry(this, nullptr);
          GenStatements(info_.literal()->body(), start);
          GenFinallyBeforeControlChange(surrounding_try_, nullptr,
                                        ControlFlowChange::Break);
          builder_.createReturnInst(builder_.getLiteralUndefined(), true);
          builder_.setInsertionBlock(
              builder_.createBasicBlock(this->curr_func_));
        },
        // emitNormalCleanup.
        []() {
          // do nothing
        },
        // emitHandler.
        [this](hermes::BasicBlock* nextBlock, hermes::TryStartInst* inst) {
          auto* catchInst = builder_.createCatchInst(inst->getContext());
          Variable* generator_object_var =
              closure_scope_->generator_object_var();
          hermes::Value* generator_object =
              BuildVariableLoad(generator_object_var, HoleCheckMode::kElided);
          hermes::Value* reject = builder_.createCallBuiltinInst(
              hermes::BuiltinMethod::HermesBuiltin_PBCAsyncGeneratorReject,
              {generator_object, catchInst});
          GenFinallyBeforeControlChange(surrounding_try_, nullptr,
                                        ControlFlowChange::Break);
          builder_.createReturnInst(reject);
        });
    builder_.setInsertionBlock(nextBlock);
  };

  std::function<void(void)> finally_body = [this]() {
    Variable* generator_object_var = closure_scope_->generator_object_var();
    hermes::Value* generator_object =
        BuildVariableLoad(generator_object_var, HoleCheckMode::kElided);
    builder_.createCallBuiltinInst(
        hermes::BuiltinMethod::HermesBuiltin_PBCGeneratorClose,
        {generator_object});
  };

  auto* nextBlock = emitTryCatchScaffolding(
      nullptr,
      // emitBody.
      [this, &try_body, &finally_body]() {
        SurroundingTry thisTry(
            this, nullptr,
            [&finally_body](AstNode*, ControlFlowChange, hermes::BasicBlock*) {
              finally_body();
            });
        try_body();
      },
      // emitNormalCleanup.
      [&finally_body]() { finally_body(); },
      // emitHandler.
      [this, &finally_body](hermes::BasicBlock* nextBlock,
                            hermes::TryStartInst* inst) {
        auto* catchInst = builder_.createCatchInst(inst->getContext());
        finally_body();
        builder_.createThrowInst(catchInst);
      });
  builder_.setInsertionBlock(nextBlock);
  builder_.createReturnInst(builder_.getLiteralUndefined(), true);
}

hermes::Value* FunctionIRTranslator::GenNullishSubExpression(
    Expression* expr, hermes::BasicBlock* exitBlock,
    hermes::AllocStackInst* temp) {
  if (expr->IsLiteralButNotNullOrUndefined()) {
    hermes::Value* value = GenExpression(expr);
    builder_.createBranchInst(exitBlock);
    builder_.setInsertionBlock(exitBlock);
    return value;
  } else if (!expr->IsNullOrUndefinedLiteral()) {
    hermes::Value* value = GenExpression(expr);
    builder_.createStoreStackInst(value, temp);
    hermes::BasicBlock* onNullish = builder_.createBasicBlock(curr_func_);
    hermes::Value* isNullish = builder_.createBinaryOperatorInst(
        value, builder_.getLiteralNull(),
        hermes::BinaryOperatorInst::OpKind::EqualKind);
    builder_.createCondBranchInst(isNullish, onNullish, exitBlock);
    builder_.setInsertionBlock(onNullish);
  }

  return nullptr;
}
bool NeedsContextInitialization(DeclarationScope* scope) {
  return scope->NeedsContext() && !scope->is_script_scope() &&
         !scope->is_module_scope();
}
void FunctionIRTranslator::Run(uintptr_t stack_limit) {
#if DEBUG
  if (enable_log_) {
    llvh::outs() << "GenerateBytecode begin\n";
  }
#endif
  // emitFunctionPrologue
  hermes::BasicBlock* entry = builder_.createBasicBlock(curr_func_);
  builder_.setLocation(curr_func_->getSourceRange().Start);
  builder_.setCurrentSourceLevelScope(nullptr);

  if (info_.literal()->kind() == FunctionKind::kDerivedConstructor) {
    check_return_block_ = builder_.createBasicBlock(curr_func_);
    builder_.setInsertionBlock(check_return_block_);
    check_return_value_ = builder_.createPhiInst({}, {});
  }

  hermes::BasicBlock* realEntry = &curr_func_->front();
  if (realEntry->empty()) {
    builder_.setInsertionBlock(realEntry);
  } else {
    builder_.setInsertionPoint(&realEntry->front());
  }

  currentIRScopeDesc_ = curr_func_->getFunctionScopeDesc();
  currentIRScope_ = builder_.createCreateScopeInst(currentIRScopeDesc_);

  // Start pumping instructions into the entry basic block.
  builder_.setInsertionBlock(entry);
  builder_.setCurrentSourceLevelScope(currentIRScopeDesc_);

  // Always create the "this" parameter. It needs to be created before we
  // initialized the ES5 capture state.
  builder_.createParameter(curr_func_, "this");

  if (closure_scope_->has_this_declaration() &&
      (closure_scope_->receiver()->IsContextSlot() ||
       closure_scope_->receiver()->IsParameter())) {
    BuildVariableInit(curr_func_->getThisParameter(),
                      closure_scope_->receiver(), HoleCheckMode::kElided);
  }
  int num = closure_scope_->num_parameters();
  for (int i = 0; i < num; i++) {
    Variable* variable = closure_scope_->parameter(i);
    std::string utf8_name;
    hermes::Identifier formalParamName =
        GenAnonymousLabelName(ToStringRef(variable->raw_name(), utf8_name));
    builder_.createParameter(curr_func_, formalParamName);
  }
  auto irParamList = curr_func_->getParameters();
  // reverse order to save in context parameter
  for (int i = num - 1; i >= 0; i--) {
    Variable* variable = closure_scope_->parameter(i);
    VarDecl::Kind declKind = getVarDeclKind(variable);
    std::string utf8_name;
    hermes::Identifier formalParamName =
        GenAnonymousLabelName(ToStringRef(variable->raw_name(), utf8_name));
    hermes::Variable* var = nullptr;
    if (variable->IsContextSlot()) {
      // assume context parameter cannot be optimized
      var = builder_.createVolatileVariable(CurrentScopeDesc(), declKind,
                                            formalParamName);
    } else {
      var = builder_.createVariable(CurrentScopeDesc(), declKind,
                                    formalParamName);
    }
    builder_.createStoreFrameInst(irParamList[i], var, currentIRScope_);
    nameTable_.insertIntoScope(variable, var);
  }

  for (int i = 0; i < closure_scope_->num_stack_slots(); i++) {
    hermes::AllocStackInst* local =
        builder_.createAllocStackInst(GenAnonymousLabelName("local"));
    builder_.createStoreStackInst(builder_.getLiteralUndefined(), local);
    locals_.push_back(local);
  }

  // GenerateBodyPrologue
  // Build the arguments object if it is used.
  GenArgumentsObject(closure_scope_->arguments());

  // Build rest arguments array if it is used.
  Variable* rest_parameter = closure_scope_->rest_parameter();
  GenRestArgumentsArray(rest_parameter);

  // Build assignment to the function name or {.this_function}
  // variables if used.

  GenThisFunctionVariable(closure_scope_->function_var(),
                          builder_.createGetClosure());
  GenThisFunctionVariable(closure_scope_->this_function_var(),
                          builder_.createGetClosure());

  // Build assignment to {new.target} variable if it is used.
  GenNewTargetVariable(closure_scope_->new_target_var());

  if (IsResumableFunction(info_.literal()->kind())) {
    hermes::Value* generator_object = builder_.createYieldGeneratorObjectInst();
    Variable* generator_object_var = closure_scope_->generator_object_var();
    BuildVariableInit(generator_object, generator_object_var,
                      HoleCheckMode::kElided);
  }

  // Create a generator object if necessary and initialize the
  // {.generator_object} variable.
  // FunctionLiteral* literal = info()->literal();
  // if (IsResumableFunction(literal->kind())) {
  //   GenGeneratorObjectVariableInitialization();
  // }
  // TODO:Visit declarations within the function scope.
  // Visit declarations within the function scope.
  if (closure_scope_->is_script_scope()) {
    GenGlobalDeclarations(closure_scope_->declarations());
  } else if (closure_scope_->is_module_scope()) {
    FATAL(__FUNCTION__);
    // GenModuleDeclarations(closure_scope_->declarations());
  } else {
    GenDeclarations(closure_scope_->declarations());
  }

  if (isNewBE_) {
    curr_func_->setExpectedParamCountIncludingThis(
        info_.num_parameters_including_this());
  } else {
    curr_func_->setExpectedParamCountIncludingThis(
        info_.literal()->function_length() + 1);
  }
  curr_func_->setFuncLength(info_.literal()->function_length());
  // reference to shared-function-info.cc get_property_estimate_from_literal
  // TODO: class constructor nof property
  int estimate = info_.literal()->expected_property_count();
  curr_func_->setExpectedNofProperties(std::min(estimate, kMaxUInt8));

  if (IsBaseConstructor(info_.literal()->kind())) {
    FunctionLiteral* literal = info_.literal();
    // The derived constructor case is handled in VisitCallSuper.
    if (literal->class_scope_has_private_brand()) {
      ClassScope* scope = info_.scope()->outer_scope()->AsClassScope();
      DCHECK_NOT_NULL(scope->brand());
      BuildPrivateBrandInitialization(curr_func_->getThisParameter(),
                                      scope->brand());
    }

    if (literal->requires_instance_members_initializer()) {
      BuildInstanceMemberInitialization(builder_.createGetClosure(),
                                        curr_func_->getThisParameter());
    }
  }

  FunctionKind kind = info_.literal()->kind();
  if (IsAsyncGeneratorFunction(kind)) {
    GenerateAsyncGeneratorFunctionBody();
    return;
  } else if (IsAsyncFunction(kind) || IsModuleWithTopLevelAwait(kind)) {
    GenerateAsyncFunctionBody();
    return;
  }

  GenStatements(info_.literal()->body());

  // Add terminator
  if (info_.literal()->kind() == FunctionKind::kDerivedConstructor) {
    hermes::BasicBlock* currBB = builder_.getInsertionBlock();
    hermes::BasicBlock* thisBB = builder_.createBasicBlock(curr_func_);
    builder_.setInsertionBlock(thisBB);
    builder_.createReturnInst(GenThisExpression(nullptr));

    builder_.setInsertionBlock(currBB);
    if (!RemainderOfBlockIsDead()) {
      builder_.createBranchInst(thisBB);
    }

    builder_.setInsertionBlock(check_return_block_);
    if (check_return_value_->getNumEntries() == 0) {
      builder_.createBranchInst(thisBB);
    } else {
      hermes::BasicBlock* defaultBB = builder_.createBasicBlock(curr_func_);
      builder_.createCondBranchInst(
          builder_.createBinaryOperatorInst(
              check_return_value_, builder_.getLiteralUndefined(),
              hermes::BinaryOperatorInst::OpKind::StrictlyEqualKind),
          thisBB, defaultBB);
      builder_.setInsertionBlock(defaultBB);
      builder_.createReturnInst(check_return_value_);
    }
  } else {
    CHECK(check_return_block_ == nullptr);
    if (!RemainderOfBlockIsDead()) {
      builder_.createReturnInst(builder_.getLiteralUndefined());
    }
  }

  if (!RemainderOfBlockIsDead()) {
    if (info_.literal()->kind() == FunctionKind::kDerivedConstructor) {
      if (nameTable_.find(closure_scope_->receiver()) != nullptr) {
        builder_.createReturnInst(builder_.createLoadFrameInst(
            nameTable_.lookup(closure_scope_->receiver()), currentIRScope_));
      } else {
        builder_.createReturnInst(builder_.getFunction()->getThisParameter());
      }
    } else {
      builder_.createReturnInst(builder_.getLiteralUndefined());
    }
  }
#if DEBUG
  if (enable_log_) {
    llvh::outs() << "GenerateBytecode end\n";
  }
#endif
}

inline hermes::UnaryOperatorInst::OpKind getUnaryOperatorInstKind(
    Token::Value kind) {
#define UNARY_OP_MAP(FROM, TO) \
  case Token::FROM:            \
    return hermes::UnaryOperatorInst::OpKind::TO;
  switch (kind) {
    UNARY_OP_MAP(kAdd, PlusKind)
    UNARY_OP_MAP(kSub, MinusKind)
    UNARY_OP_MAP(kBitNot, TildeKind)
    UNARY_OP_MAP(kInc, IncKind)
    UNARY_OP_MAP(kDec, DecKind)
    UNARY_OP_MAP(kNot, BangKind)
    UNARY_OP_MAP(kDelete, DeleteKind)
    UNARY_OP_MAP(kVoid, VoidKind)
    UNARY_OP_MAP(kTypeOf, TypeofKind)
    default:
      FATAL(__FUNCTION__);
  }
#undef UNARY_OP_MAP
}

inline hermes::BinaryOperatorInst::OpKind getBinaryOperatorInstKind(
    Token::Value kind) {
#define BINARY_OP_MAP(FROM, TO) \
  case Token::FROM:             \
    return hermes::BinaryOperatorInst::OpKind::TO;
  switch (kind) {
    BINARY_OP_MAP(kEq, EqualKind)
    BINARY_OP_MAP(kEqStrict, StrictlyEqualKind)
    BINARY_OP_MAP(kNotEq, NotEqualKind)
    BINARY_OP_MAP(kNotEqStrict, StrictlyNotEqualKind)
    BINARY_OP_MAP(kLessThan, LessThanKind)
    BINARY_OP_MAP(kGreaterThan, GreaterThanKind)
    BINARY_OP_MAP(kLessThanEq, LessThanOrEqualKind)
    BINARY_OP_MAP(kGreaterThanEq, GreaterThanOrEqualKind)
    BINARY_OP_MAP(kShl, LeftShiftKind)
    BINARY_OP_MAP(kSar, RightShiftKind)
    BINARY_OP_MAP(kShr, UnsignedRightShiftKind)
    BINARY_OP_MAP(kAdd, AddKind)
    BINARY_OP_MAP(kSub, SubtractKind)
    BINARY_OP_MAP(kMul, MultiplyKind)
    BINARY_OP_MAP(kDiv, DivideKind)
    BINARY_OP_MAP(kMod, ModuloKind)
    BINARY_OP_MAP(kBitOr, OrKind)
    BINARY_OP_MAP(kBitXor, XorKind)
    BINARY_OP_MAP(kBitAnd, AndKind)
    BINARY_OP_MAP(kExp, ExponentiationKind)
    BINARY_OP_MAP(kOr, AssignShortCircuitOrKind)
    BINARY_OP_MAP(kAnd, AssignShortCircuitAndKind)
    BINARY_OP_MAP(kNullish, AssignNullishCoalesceKind)
    BINARY_OP_MAP(kIn, InKind)
    BINARY_OP_MAP(kInstanceOf, InstanceOfKind)
    default: {
      FATAL(__FUNCTION__);
    }
  }
#undef BINARY_OP_MAP
}

std::shared_ptr<hermes::Context> CreateContext() {
  hermes::CodeGenerationSettings codeGenOpts;
  codeGenOpts.unlimitedRegisters = false;
  // codeGenOpts.dumpAfter.all = true;
  hermes::OptimizationSettings optimizationOpts;
  std::unique_ptr<hermes::Context::ResolutionTable> resolutionTable = nullptr;
  std::vector<uint32_t> segments;

  auto context = std::make_shared<hermes::Context>(
      codeGenOpts, optimizationOpts, std::move(resolutionTable),
      std::move(segments));
  return context;
}

class Translator {
 public:
  Translator(i::Isolate* isolate, i::ParseInfo& parse_info,
             i::Handle<i::Script> script, bool enableBlockScope, bool isNewBE,
             bool enable_log = false)
      : isolate_(isolate),
        parse_info_(parse_info),
        script_(script),
        context_(CreateContext()),
        module_(context_),
        builder_(&module_),
        isNewBE_(isNewBE),
        enable_log_(enable_log) {
    enableBlockScoping = enableBlockScope;
  }

  void Run();

  //  private:
  i::Isolate* isolate_;
  i::ParseInfo& parse_info_;
  i::Handle<i::Script> script_;

  // IR related
  std::shared_ptr<hermes::Context> context_;
  hermes::Module module_;
  hermes::IRBuilder builder_;
  NameTable nameTable_;
  bool isNewBE_;
  bool enable_log_;
};

void Translator::Run() {
  EagerInnerLiteralsType functions_to_compile;
  auto top = builder_.createTopLevelFunction(
      module_.getInitialScope(),
      parse_info_.literal()->language_mode() == LanguageMode::kStrict);
  // see parse-info.cc
  top->setFunctionSyntaxKind(script_->is_wrapped()
                                 ? portable::FunctionSyntaxKind::kWrapped
                                 : portable::FunctionSyntaxKind::kDeclaration);

  functions_to_compile.emplace_back(parse_info_.literal(), top);
  module_.setTopLevelFunction(top);

  bool is_first = true;
  while (!functions_to_compile.empty()) {
    auto [literal, func] = functions_to_compile.back();
    functions_to_compile.pop_back();
    if (is_first) {
      //      // We get the first SharedFunctionInfo directly as
      //      outer_shared_info
      //      // rather than with Compiler::GetSharedFunctionInfo, to support
      //      // placeholder SharedFunctionInfos that aren't on the script's
      //      SFI list. CHECK_EQ(literal->function_literal_id(),
      //                outer_shared_info->function_literal_id());
      //      shared_info = outer_shared_info;
      is_first = false;
    } else {
      // TODO:
    }
#if DEBUG
    if (enable_log_) {
      StdoutStream os;
      std::unique_ptr<char[]> name = literal->GetDebugName();
      os << "[generating bytecode for function: " << name.get() << "]"
         << std::endl;
      os << "--- AST ---" << std::endl
         << AstPrinter(parse_info_.stack_limit()).PrintProgram(literal)
         << std::endl;
    }
#endif  // DEBUG

    FunctionIRTranslator trans(isolate_, parse_info_, script_, context_,
                               module_, builder_, &functions_to_compile,
                               literal, func, nameTable_, isNewBE_,
                               enable_log_);
    trans.Run(parse_info_.stack_limit());
  }

#if DEBUG
  if (enable_log_) {
    llvh::outs() << "GenerateBytecode ir dump before opt\n";
    module_.dump();
  }
#endif
  // generateIRFromESTree
  bool failedVerification = hermes::verifyModule(module_, &llvh::errs());
  if (failedVerification) {
    module_.dump();
    llvh::outs() << "GenerateBytecode verifyModule fail\n";
    FATAL(__FUNCTION__);
  }

  hermes::runFullOptimizationPasses(module_);
#if DEBUG
  if (enable_log_) {
    llvh::outs() << "GenerateBytecode ir dump after opt\n";
    module_.dump();
  }

#endif
#if 0
  // Enable scope tree dumping if needed
  module_.getInitialScope()->getInnerScopes()[0]->dumpRecursive(0);
#endif
  // generateIRFromESTree
  failedVerification = hermes::verifyModule(module_, &llvh::errs());
  if (failedVerification) {
    module_.dump();
    llvh::outs() << "GenerateBytecode verifyModule fail\n";
    FATAL(__FUNCTION__);
  }
}

uintptr_t ProduceHBCModuleWithIRNewBE(i::Isolate* isolate,
                                      i::Handle<i::String> source,
                                      std::vector<uint8_t>* pbc_buffer,
                                      bool block_scope, bool enable_log) {
  i::VMState<v8::PARSER> state(isolate);
  // Set up ParseInfo.
  i::UnoptimizedCompileState compile_state;
  i::ReusableUnoptimizedCompileState reusable_state(isolate);

  i::UnoptimizedCompileFlags flags =
      i::UnoptimizedCompileFlags::ForToplevelCompile(
          isolate, true, i::construct_language_mode(i::v8_flags.use_strict),
          i::REPLMode::kNo, ScriptType::kClassic, i::v8_flags.lazy);
  flags.set_is_eager(true);

  i::ParseInfo parse_info(isolate, flags, &compile_state, &reusable_state);

  i::Handle<i::Script> script = parse_info.CreateScript(
      isolate, source, i::kNullMaybeHandle, ScriptOriginOptions());
  if (!i::parsing::ParseProgram(&parse_info, script, isolate,
                                i::parsing::ReportStatisticsMode::kYes)) {
    parse_info.pending_error_handler()->PrepareErrors(
        isolate, parse_info.ast_value_factory());
    parse_info.pending_error_handler()->ReportErrors(isolate, script);
    return 1;
  }

  Translator translator(isolate, parse_info, script, block_scope, true,
                        enable_log);
  translator.Run();
  // new backend convert module to pbc
  hermes::BytecodeGenerationOptions genOptions{hermes::Execute};
  genOptions.optimizationEnabled = true;
  return hermes::pbc::generateBytecodeModule(
      &translator.module_, translator.module_.getTopLevelFunction(), genOptions,
      pbc_buffer);
}
}  // namespace aot
}  // namespace v8
